Browse Source

multiple improvements

* Improved translation system
* Added wiki list for interwikis
Markus-Rost 4 years ago
parent
commit
809af67a96

+ 19 - 14
bot.js

@@ -16,7 +16,7 @@ global.got = require('got').extend( {
 	}
 } );
 
-const {defaultSettings} = require('./util/default.json');
+const {defaultSettings, wikiProjects} = require('./util/default.json');
 const Lang = require('./util/i18n.js');
 const newMessage = require('./util/newMessage.js');
 global.patreons = {};
@@ -65,13 +65,13 @@ client.on( 'shardDisconnect', () => client.ready = false );
 
 
 String.prototype.noWiki = function(href) {
-	if ( !href ) return false;
-	else if ( this.startsWith( 'https://www.' ) ) return true;
-	else if ( this.endsWith( '.gamepedia.com/' ) ) return 'https://www.gamepedia.com/' === href;
-	else return [
+	if ( !href ) return true;
+	else if ( this.startsWith( 'https://www.' ) && ( this.endsWith( '.gamepedia.com/' ) || this.isFandom() ) ) return true;
+	else if ( this.isFandom() ) return [
 		this.replace( /^https:\/\/([a-z\d-]{1,50}\.(?:fandom\.com|wikia\.org))\/(?:[a-z-]{1,8}\/)?$/, 'https://community.fandom.com/wiki/Community_Central:Not_a_valid_community?from=$1' ),
 		this + 'language-wikis'
 	].includes( href.replace( /Unexpected token < in JSON at position 0 in "([^ ]+)"/, '$1' ) );
+	else return false;
 };
 
 String.prototype.isFandom = function() {
@@ -102,9 +102,14 @@ Discord.Message.prototype.uploadFiles = function() {
 String.prototype.toLink = function(title = '', querystring = '', fragment = '', path, isMarkdown = false) {
 	var linksuffix = ( querystring ? '?' + querystring : '' ) + ( fragment ? '#' + fragment.toSection() : '' );
 	if ( path ) return ( path.server.startsWith( '//' ) ? 'https:' : '' ) + path.server + path.articlepath.replaceSave( '$1', title.toTitle(isMarkdown, path.articlepath.includes( '?' )) ) + ( path.articlepath.includes( '?' ) && linksuffix.startsWith( '?' ) ? '&' + linksuffix.substring(1) : linksuffix );
-	else if ( this.endsWith( '.gamepedia.com/' ) ) return this + title.toTitle(isMarkdown) + linksuffix;
-	else if ( this.isFandom() ) return this + 'wiki/' + title.toTitle(isMarkdown) + linksuffix;
-	else return this + 'index.php?title=' + title.toTitle(isMarkdown, true) + ( linksuffix.startsWith( '?' ) ? '&' + linksuffix.substring(1) : linksuffix );
+	if ( this.endsWith( '.gamepedia.com/' ) ) return this + title.toTitle(isMarkdown) + linksuffix;
+	if ( this.isFandom() ) return this + 'wiki/' + title.toTitle(isMarkdown) + linksuffix;
+	if ( wikiProjects.some( project => this.includes( project.name ) ) ) {
+		let project = wikiProjects.find( project => this.includes( project.name ) );
+		let regex = this.match( new RegExp( project.regex ) );
+		if ( regex ) return 'https://' + regex[1] + project.articlePath + title.toTitle(isMarkdown, project.articlePath.includes( '?' )) + ( project.articlePath.includes( '?' ) && linksuffix.startsWith( '?' ) ? '&' + linksuffix.substring(1) : linksuffix );
+	}
+	return this + 'index.php?title=' + title.toTitle(isMarkdown, true) + ( linksuffix.startsWith( '?' ) ? '&' + linksuffix.substring(1) : linksuffix );
 };
 
 String.prototype.toDescLink = function(title = '') {
@@ -262,21 +267,21 @@ String.prototype.hasPrefix = function(prefix, flags = '') {
 };
 
 client.on( 'message', msg => {
-	if ( isStop || msg.type !== 'DEFAULT' || msg.system || msg.webhookID || msg.author.id === client.user.id ) return;
+	if ( isStop || msg.type !== 'DEFAULT' || msg.system || msg.webhookID || msg.user.bot || msg.author.id === msg.client.user.id ) return;
 	if ( !msg.content.hasPrefix(( msg.channel.type === 'text' && patreons[msg.guild.id] || process.env.prefix ), 'm') ) {
 		if ( msg.content === process.env.prefix + 'help' && ( msg.isAdmin() || msg.isOwner() ) ) {
-			if ( msg.channel.permissionsFor(client.user).has('SEND_MESSAGES') ) {
+			if ( msg.channel.permissionsFor(msg.client.user).has('SEND_MESSAGES') ) {
 				console.log( msg.guild.name + ': ' + msg.content );
 				db.get( 'SELECT lang FROM discord WHERE guild = ? AND (channel = ? OR channel IS NULL) ORDER BY channel DESC', [msg.guild.id, msg.channel.id], (dberror, row) => {
 					if ( dberror ) console.log( '- Error while getting the lang: ' + dberror );
-					msg.replyMsg( new Lang(( row || defaultSettings ).lang).get('prefix').replaceSave( /%s/g, patreons[msg.guild.id] ), {}, true );
+					msg.replyMsg( new Lang(( row || defaultSettings ).lang).get('prefix', patreons[msg.guild.id]), {}, true );
 				} );
 			}
 		}
 		if ( !( msg.content.includes( '[[' ) && msg.content.includes( ']]' ) ) && !( msg.content.includes( '{{' ) && msg.content.includes( '}}' ) ) ) return;
 	}
 	if ( msg.channel.type === 'text' ) {
-		var permissions = msg.channel.permissionsFor(client.user);
+		var permissions = msg.channel.permissionsFor(msg.client.user);
 		var missing = permissions.missing(['SEND_MESSAGES','ADD_REACTIONS','USE_EXTERNAL_EMOJIS','READ_MESSAGE_HISTORY']);
 		if ( missing.length ) {
 			if ( msg.isAdmin() || msg.isOwner() ) {
@@ -319,14 +324,14 @@ client.on( 'voiceStateUpdate', (olds, news) => {
 		var oldrole = olds.member.roles.cache.find( role => role.name === lang.get('channel') + ' – ' + olds.channel.name );
 		if ( oldrole && oldrole.comparePositionTo(olds.guild.me.roles.highest) < 0 ) {
 			console.log( olds.guild.id + ': ' + olds.member.id + ' left the voice channel "' + olds.channel.id + '".' );
-			olds.member.roles.remove( oldrole, lang.get('left').replaceSave( '%1$s', olds.member.displayName ).replaceSave( '%2$s', olds.channel.name ) ).catch(log_error);
+			olds.member.roles.remove( oldrole, lang.get('left', olds.member.displayName, olds.channel.name) ).catch(log_error);
 		}
 	}
 	if ( news.member && news.channel ) {
 		var newrole = news.guild.roles.cache.find( role => role.name === lang.get('channel') + ' – ' + news.channel.name );
 		if ( newrole && newrole.comparePositionTo(news.guild.me.roles.highest) < 0 ) {
 			console.log( news.guild.id + ': ' + news.member.id + ' joined the voice channel "' + news.channel.id + '".' );
-			news.member.roles.add( newrole, lang.get('join').replaceSave( '%1$s', news.member.displayName ).replaceSave( '%2$s', news.channel.name ) ).catch(log_error);
+			news.member.roles.add( newrole, lang.get('join', news.member.displayName, news.channel.name) ).catch(log_error);
 		}
 	}
 } );

+ 4 - 0
cmds/eval.js

@@ -30,6 +30,10 @@ function database(sql, sqlargs = []) {
 	} );
 }
 
+function updateAllSites() {
+	return require('../util/allSites.js').update();
+}
+
 function removePatreons(guild, msg) {
 	try {
 		if ( !guild || !msg ) return 'removePatreons(guild, msg) – No guild or message provided!';

+ 1 - 1
cmds/info.js

@@ -3,7 +3,7 @@ const help_server = require('../functions/helpserver.js');
 function cmd_info(lang, msg, args, line, wiki) {
 	if ( args.join('') ) this.LINK(lang, msg, line, wiki);
 	else {
-		msg.sendChannel( lang.get('disclaimer').replaceSave( '%s', ( msg.channel.type === 'text' && msg.guild.members.cache.get(process.env.owner) || '*MarkusRost*' ).toString() ) + '\n<' + process.env.patreon + '>' );
+		msg.sendChannel( lang.get('disclaimer', '*MarkusRost*') + '\n<' + process.env.patreon + '>' );
 		help_server(lang, msg);
 		this.invite(lang, msg, args, line, wiki);
 	}

+ 38 - 20
cmds/settings.js

@@ -1,5 +1,5 @@
 const {MessageEmbed} = require('discord.js');
-const {defaultSettings} = require('../util/default.json');
+const {defaultSettings, wikiProjects} = require('../util/default.json');
 const Lang = require('../util/i18n.js');
 const allLangs = Lang.allLangs();
 var db = require('../util/database.js');
@@ -9,7 +9,7 @@ const getAllSites = require('../util/allSites.js');
 getAllSites.then( sites => allSites = sites );
 
 function cmd_settings(lang, msg, args, line, wiki) {
-	if ( !allSites.length ) getAllSites.get().then( sites => allSites = sites );
+	if ( !allSites.length ) getAllSites.update();
 	if ( !msg.isAdmin() ) return msg.reactEmoji('❌');
 	
 	db.all( 'SELECT channel, lang, wiki, prefix, inline FROM discord WHERE guild = ? ORDER BY channel DESC', [msg.guild.id], (error, rows) => {
@@ -21,7 +21,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 		var guild = rows.find( row => !row.channel );
 		if ( !guild ) guild = Object.assign({prefix: process.env.prefix}, defaultSettings);
 		var prefix = guild.prefix;
-		var text = lang.get('settings.missing').replaceSave( '%1$s', '`' + prefix + 'settings lang`' ).replaceSave( '%2$s', '`' + prefix + 'settings wiki`' );
+		var text = lang.get('settings.missing', '`' + prefix + 'settings lang`', '`' + prefix + 'settings wiki`');
 		if ( rows.length ) {
 			text = lang.get('settings.current') + '\n' + lang.get('settings.currentlang') + ' `' + allLangs.names[guild.lang][1] + '` - `' + prefix + 'settings lang`';
 			if ( msg.guild.id in patreons ) text += '\n' + lang.get('settings.currentprefix') + ' `' + prefix + '` - `' + prefix + 'settings prefix`';
@@ -60,19 +60,19 @@ function cmd_settings(lang, msg, args, line, wiki) {
 		
 		if ( args[0] === 'wiki' ) {
 			prelang += 'wiki';
-			var wikihelp = '\n' + lang.get('settings.wikihelp').replaceSave( '%s', prefix + 'settings ' + prelang );
+			var wikihelp = '\n' + lang.get('settings.wikihelp', prefix + 'settings ' + prelang);
 			if ( !args[1] ) {
 				if ( !rows.length ) return msg.replyMsg( lang.get('settings.wikimissing') + wikihelp, {}, true );
 				else return msg.replyMsg( lang.get('settings.' + prelang) + ' ' + ( channel || guild ).wiki + wikihelp, {}, true );
 			}
 			var wikinew = '';
 			var isForced = false;
-			var regex = args[1].match( /^(?:(?:https?:)?\/\/)?([a-z\d-]{1,50}\.(?:gamepedia\.com|(?:fandom\.com|wikia\.org)(?:(?!\/wiki\/)\/[a-z-]{2,8})?))(?:\/|$)/ );
+			var regex = args[1].match( /^(?:(?:https?:)?\/\/)?([a-z\d-]{1,50}\.(?:gamepedia\.com|(?:fandom\.com|wikia\.org)(?:(?!\/wiki\/)\/[a-z-]{2,12})?))(?:\/|$)/ );
 			if ( regex ) wikinew = 'https://' + regex[1] + '/';
 			else if ( allSites.some( site => site.wiki_domain === args[1] + '.gamepedia.com' ) ) {
 				wikinew = 'https://' + args[1] + '.gamepedia.com/';
 			}
-			else if ( /^(?:[a-z-]{1,8}\.)?[a-z\d-]{1,50}$/.test(args[1]) ) {
+			else if ( /^(?:[a-z-]{2,12}\.)?[a-z\d-]{1,50}$/.test(args[1]) ) {
 				if ( !args[1].includes( '.' ) ) wikinew = 'https://' + args[1] + '.fandom.com/';
 				else wikinew = 'https://' + args[1].split('.')[1] + '.fandom.com/' + args[1].split('.')[0] + '/';
 			}
@@ -80,6 +80,11 @@ function cmd_settings(lang, msg, args, line, wiki) {
 				args[1] = args[1].replace( /^<?(?:https?:)?\/\//, 'https://' );
 				var value = args[1].split(/>? /);
 				if ( value.length === 2 && value[1] === '--force' ) isForced = true;
+				if ( wikiProjects.some( project => value[0].includes( project.name ) ) ) {
+					let project = wikiProjects.find( project => value[0].includes( project.name ) );
+					let regex = value[0].match( new RegExp( project.regex ) );
+					if ( regex ) value[0] = 'https://' + regex[1] + project.scriptPath;
+				}
 				value[0] = value[0].replace( /\/(?:api|index)\.php(?:|\?.*)$/, '/' );
 				wikinew = value[0] + ( value[0].endsWith( '/' ) ? '' : '/' );
 			}
@@ -129,21 +134,21 @@ function cmd_settings(lang, msg, args, line, wiki) {
 							console.log( '- This wiki is using ' + body.query.general.generator + '.' );
 							notice.push({
 								name: 'MediaWiki',
-								value: lang.get('test.MediaWiki').replaceSave( '%1$s', '[MediaWiki 1.30](https://www.mediawiki.org/wiki/MediaWiki_1.30)' ).replaceSave( '%2$s', body.query.general.generator )
+								value: lang.get('test.MediaWiki', '[MediaWiki 1.30](https://www.mediawiki.org/wiki/MediaWiki_1.30)', body.query.general.generator)
 							});
 						}
 						if ( !body.query.extensions.some( extension => extension.name === 'TextExtracts' ) ) {
 							console.log( '- This wiki is missing Extension:TextExtracts.' );
 							notice.push({
 								name: 'TextExtracts',
-								value: lang.get('test.TextExtracts').replaceSave( '%s', '[TextExtracts](https://www.mediawiki.org/wiki/Extension:TextExtracts)' )
+								value: lang.get('test.TextExtracts', '[TextExtracts](https://www.mediawiki.org/wiki/Extension:TextExtracts)')
 							});
 						}
 						if ( !body.query.extensions.some( extension => extension.name === 'PageImages' ) ) {
 							console.log( '- This wiki is missing Extension:PageImages.' );
 							notice.push({
 								name: 'PageImages',
-								value: lang.get('test.PageImages').replaceSave( '%s', '[PageImages](https://www.mediawiki.org/wiki/Extension:PageImages)' )
+								value: lang.get('test.PageImages', '[PageImages](https://www.mediawiki.org/wiki/Extension:PageImages)')
 							});
 						}
 						if ( notice.length ) {
@@ -177,12 +182,17 @@ function cmd_settings(lang, msg, args, line, wiki) {
 						}
 						console.log( '- Settings successfully updated.' );
 						if ( channel ) channel.wiki = wikinew;
-						else guild.wiki = wikinew;
+						else {
+							rows.forEach( row => {
+								if ( row.channel && row.wiki === guild.wiki ) row.wiki = wikinew;
+							} );
+							guild.wiki = wikinew;
+						}
 						if ( channel || !rows.some( row => row.channel === msg.channel.id ) ) wiki = wikinew;
 						if ( reaction ) reaction.removeEmoji();
 						msg.replyMsg( lang.get('settings.' + prelang + 'changed') + ' ' + wikinew + wikihelp, {embed}, true );
 						var channels = rows.filter( row => row.channel && row.lang === guild.lang && row.wiki === guild.wiki && row.prefix === guild.prefix && row.inline === guild.inline ).map( row => row.channel );
-						if ( channels.length ) db.run( 'DELETE FROM discord WHERE channel IN (' + channels.map( row => '?' ).join('|') + ')', channels, function (delerror) {
+						if ( channels.length ) db.run( 'DELETE FROM discord WHERE channel IN (' + channels.map( row => '?' ).join(', ') + ')', channels, function (delerror) {
 							if ( delerror ) {
 								console.log( '- Error while removing the settings: ' + delerror );
 								return delerror;
@@ -202,7 +212,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 		if ( args[0] === 'lang' ) {
 			if ( channel && !( msg.guild.id in patreons ) ) return msg.replyMsg( lang.get('patreon') + ' <' + process.env.patreon + '>', {}, true );
 			prelang += 'lang';
-			var langhelp = '\n' + lang.get('settings.langhelp').replaceSave( '%s', prefix + 'settings ' + prelang ) + ' `' + Object.values(allLangs.names).map( val => val[0] ).join('`, `') + '`';
+			var langhelp = '\n' + lang.get('settings.langhelp', prefix + 'settings ' + prelang) + ' `' + Object.values(allLangs.names).map( val => val[0] ).join('`, `') + '`';
 			if ( !args[1] ) {
 				return msg.replyMsg( lang.get('settings.' + prelang) + ' `' + allLangs.names[( channel || guild ).lang][1] + '`' + langhelp, {}, true );
 			}
@@ -235,13 +245,16 @@ function cmd_settings(lang, msg, args, line, wiki) {
 				console.log( '- Settings successfully updated.' );
 				if ( channel ) channel.lang = allLangs.map[args[1]];
 				else {
+					rows.forEach( row => {
+						if ( row.channel && row.lang === guild.lang ) row.lang = allLangs.map[args[1]];
+					} );
 					guild.lang = allLangs.map[args[1]];
 					if ( msg.guild.id in voice ) voice[msg.guild.id] = guild.lang;
 				}
 				if ( channel || !( msg.guild.id in patreons ) || !rows.some( row => row.channel === msg.channel.id ) ) lang = new Lang(allLangs.map[args[1]]);
-				msg.replyMsg( lang.get('settings.' + prelang + 'changed') + ' `' + allLangs.names[allLangs.map[args[1]]][1] + '`\n' + lang.get('settings.langhelp').replaceSave( '%s', prefix + 'settings ' + prelang ) + ' `' + Object.values(allLangs.names).join('`, `') + '`', {}, true );
+				msg.replyMsg( lang.get('settings.' + prelang + 'changed') + ' `' + allLangs.names[allLangs.map[args[1]]][1] + '`\n' + lang.get('settings.langhelp', prefix + 'settings ' + prelang) + ' `' + Object.values(allLangs.names).map( val => val[0] ).join('`, `') + '`', {}, true );
 				var channels = rows.filter( row => row.channel && row.lang === guild.lang && row.wiki === guild.wiki && row.prefix === guild.prefix && row.inline === guild.inline ).map( row => row.channel );
-				if ( channels.length ) db.run( 'DELETE FROM discord WHERE channel IN (' + channels.map( row => '?' ).join('|') + ')', channels, function (delerror) {
+				if ( channels.length ) db.run( 'DELETE FROM discord WHERE channel IN (' + channels.map( row => '?' ).join(', ') + ')', channels, function (delerror) {
 					if ( delerror ) {
 						console.log( '- Error while removing the settings: ' + delerror );
 						return delerror;
@@ -255,7 +268,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 			if ( !( msg.guild.id in patreons ) ) {
 				return msg.replyMsg( lang.get('patreon') + ' <' + process.env.patreon + '>', {}, true );
 			}
-			var prefixhelp = '\n' + lang.get('settings.prefixhelp').replaceSave( '%s', prefix + 'settings prefix' );
+			var prefixhelp = '\n' + lang.get('settings.prefixhelp', prefix + 'settings prefix');
 			args[1] = args[1].replace( /(?<!\\)_$/, ' ' ).replace( /\\([_\W])/g, '$1' );
 			if ( !args[1].trim() ) {
 				return msg.replyMsg( lang.get('settings.prefix') + ' `' + prefix.replace( / $/, '_' ) + '`' + prefixhelp, {}, true );
@@ -278,7 +291,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 				console.log( '- Settings successfully updated.' );
 				guild.prefix = args[1];
 				msg.client.shard.broadcastEval( `global.patreons['${msg.guild.id}'] = '${args[1]}'` );
-				msg.replyMsg( lang.get('settings.prefixchanged') + ' `' + args[1].replace( / $/, '_' ) + '`\n' + lang.get('settings.prefixhelp').replaceSave( '%s', args[1] + 'settings prefix' ), {}, true );
+				msg.replyMsg( lang.get('settings.prefixchanged') + ' `' + args[1].replace( / $/, '_' ) + '`\n' + lang.get('settings.prefixhelp', args[1] + 'settings prefix'), {}, true );
 			} );
 		}
 		
@@ -286,7 +299,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 			if ( channel && !( msg.guild.id in patreons ) ) return msg.replyMsg( lang.get('patreon') + ' <' + process.env.patreon + '>', {}, true );
 			prelang += 'inline';
 			var toggle = 'inline ' + ( ( channel || guild ).inline ? 'disabled' : 'enabled' );
-			var inlinehelp = '\n' + lang.get('settings.' + toggle + '.help').replaceSave( '%1$s', prefix + 'settings ' + prelang + ' toggle' ).replaceSave( /%2\$s/g, lang.get('search.page') );
+			var inlinehelp = '\n' + lang.get('settings.' + toggle + '.help', prefix + 'settings ' + prelang + ' toggle', lang.get('search.page'));
 			if ( args[1] !== 'toggle' ) {
 				return msg.replyMsg( lang.get('settings.' + toggle + '.' + prelang) + inlinehelp, {}, true );
 			}
@@ -312,11 +325,16 @@ function cmd_settings(lang, msg, args, line, wiki) {
 				}
 				console.log( '- Settings successfully updated.' );
 				if ( channel ) channel.inline = value;
-				else guild.inline = value;
+				else {
+					rows.forEach( row => {
+						if ( row.channel && row.inline === guild.inline ) row.inline = value;
+					} );
+					guild.inline = value;
+				}
 				toggle = 'inline ' + ( ( channel || guild ).inline ? 'disabled' : 'enabled' );
-				msg.replyMsg( lang.get('settings.' + toggle + '.' + prelang + 'changed') + '\n' + lang.get('settings.' + toggle + '.help').replaceSave( '%1$s', prefix + 'settings ' + prelang + ' toggle' ).replaceSave( /%2\$s/g, lang.get('search.page') ), {}, true );
+				msg.replyMsg( lang.get('settings.' + toggle + '.' + prelang + 'changed') + '\n' + lang.get('settings.' + toggle + '.help', prefix + 'settings ' + prelang + ' toggle', lang.get('search.page')), {}, true );
 				var channels = rows.filter( row => row.channel && row.lang === guild.lang && row.wiki === guild.wiki && row.prefix === guild.prefix && row.inline === guild.inline ).map( row => row.channel );
-				if ( channels.length ) db.run( 'DELETE FROM discord WHERE channel IN (' + channels.map( row => '?' ).join('|') + ')', channels, function (delerror) {
+				if ( channels.length ) db.run( 'DELETE FROM discord WHERE channel IN (' + channels.map( row => '?' ).join(', ') + ')', channels, function (delerror) {
 					if ( delerror ) {
 						console.log( '- Error while removing the settings: ' + delerror );
 						return delerror;

+ 3 - 3
cmds/test.js

@@ -35,15 +35,15 @@ function cmd_test(lang, msg, args, line, wiki) {
 				else if ( ( msg.isAdmin() || msg.isOwner() ) && !wiki.isFandom() ) {
 					if ( body.query.general.generator.replace( /^MediaWiki 1\.(\d\d).*$/, '$1' ) <= 30 ) {
 						console.log( '- This wiki is using ' + body.query.general.generator + '.' );
-						notice.push(lang.get('test.MediaWiki').replaceSave( '%1$s', '[MediaWiki 1.30](https://www.mediawiki.org/wiki/MediaWiki_1.30)' ).replaceSave( '%2$s', body.query.general.generator ));
+						notice.push(lang.get('test.MediaWiki', '[MediaWiki 1.30](https://www.mediawiki.org/wiki/MediaWiki_1.30)', body.query.general.generator));
 					}
 					if ( !body.query.extensions.some( extension => extension.name === 'TextExtracts' ) ) {
 						console.log( '- This wiki is missing Extension:TextExtracts.' );
-						notice.push(lang.get('test.TextExtracts').replaceSave( '%s', '[TextExtracts](https://www.mediawiki.org/wiki/Extension:TextExtracts)' ));
+						notice.push(lang.get('test.TextExtracts', '[TextExtracts](https://www.mediawiki.org/wiki/Extension:TextExtracts)'));
 					}
 					if ( !body.query.extensions.some( extension => extension.name === 'PageImages' ) ) {
 						console.log( '- This wiki is missing Extension:PageImages.' );
-						notice.push(lang.get('test.PageImages').replaceSave( '%s', '[PageImages](https://www.mediawiki.org/wiki/Extension:PageImages)' ));
+						notice.push(lang.get('test.PageImages', '[PageImages](https://www.mediawiki.org/wiki/Extension:PageImages)'));
 					}
 				}
 				embed.addField( wiki.toLink( '', '', '', body?.query?.general ), ping );

+ 3 - 3
cmds/verification.js

@@ -202,7 +202,7 @@ function cmd_verification(lang, msg, args, line, wiki) {
 				} ) );
 			}
 		}
-		return msg.sendChannel( '<@' + msg.author.id + '>, ' + lang.get('verification.current_selected').replace( '%1', row.configid ) + formatVerification(true) +'\n\n' + lang.get('verification.delete_current') + '\n`' + prefix + 'verification ' + row.configid + ' delete`', {split:true}, true );
+		return msg.sendChannel( '<@' + msg.author.id + '>, ' + lang.get('verification.current_selected', row.configid) + formatVerification(true) +'\n\n' + lang.get('verification.delete_current') + '\n`' + prefix + 'verification ' + row.configid + ' delete`', {split:true}, true );
 		
 		function formatVerification(showCommands, hideNotice, {
 			configid,
@@ -227,13 +227,13 @@ function cmd_verification(lang, msg, args, line, wiki) {
 			verification_text += '\n' + lang.get('verification.rename') + ' *`' + lang.get('verification.' + ( rename ? 'enabled' : 'disabled')) + '`*';
 			if ( showCommands ) verification_text += ' ' + lang.get('verification.toggle') + '\n`' + prefix + 'verification ' + row.configid + ' rename`\n';
 			if ( !hideNotice && rename && !msg.guild.me.permissions.has('MANAGE_NICKNAMES') ) {
-				verification_text += '\n\n' + lang.get('verification.rename_no_permission').replaceSave( '%s', msg.guild.me.toString() );
+				verification_text += '\n\n' + lang.get('verification.rename_no_permission', msg.guild.me.toString());
 			}
 			if ( !hideNotice && role.split('|').some( role => msg.guild.me.roles.highest.comparePositionTo(role) <= 0 ) ) {
 				verification_text += '\n';
 				role.split('|').forEach( role => {
 					if ( msg.guild.me.roles.highest.comparePositionTo(role) <= 0 ) {
-						verification_text += '\n' + lang.get('verification.role_too_high').replaceSave( '%1$s', '<@&' + role + '>' ).replaceSave( '%2$s', msg.guild.me.toString() );
+						verification_text += '\n' + lang.get('verification.role_too_high', '<@&' + role + '>', msg.guild.me.toString());
 					}
 				} );
 			}

+ 32 - 28
cmds/verify.js

@@ -58,8 +58,8 @@ function cmd_verify(lang, msg, args, line, wiki) {
 			embed.setAuthor( body.query.general.sitename );
 			if ( body.query.users.length !== 1 || queryuser.missing !== undefined || queryuser.invalid !== undefined ) {
 				username = ( body.query.users.length === 1 ? queryuser.name : username );
-				embed.setTitle( username.escapeFormatting() ).setColor('#0000FF').setDescription( lang.get('verify.user_missing').replaceSave( '%s', username.escapeFormatting() ) );
-				msg.replyMsg( lang.get('verify.user_missing_reply').replaceSave( '%s', username.escapeFormatting() ), {embed}, false, false );
+				embed.setTitle( username.escapeFormatting() ).setColor('#0000FF').setDescription( lang.get('verify.user_missing', username.escapeFormatting()) );
+				msg.replyMsg( lang.get('verify.user_missing_reply', username.escapeFormatting()), {embed}, false, false );
 				
 				if ( reaction ) reaction.removeEmoji();
 				return;
@@ -68,8 +68,8 @@ function cmd_verify(lang, msg, args, line, wiki) {
 			var pagelink = wiki.toLink('User:' + username, '', '', body.query.general, true);
 			embed.setTitle( username.escapeFormatting() ).setURL( pagelink );
 			if ( queryuser.blockexpiry ) {
-				embed.setColor('#FF0000').setDescription( lang.get('verify.user_blocked').replaceSave( '%s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ) );
-				msg.replyMsg( lang.get('verify.user_blocked_reply').replaceSave( '%s', username.escapeFormatting() ), {embed}, false, false );
+				embed.setColor('#FF0000').setDescription( lang.get('verify.user_blocked', '[' + username.escapeFormatting() + '](' + pagelink + ')') );
+				msg.replyMsg( lang.get('verify.user_blocked_reply', username.escapeFormatting()), {embed}, false, false );
 				
 				if ( reaction ) reaction.removeEmoji();
 				return;
@@ -93,22 +93,22 @@ function cmd_verify(lang, msg, args, line, wiki) {
 					if ( wiki.endsWith( '.gamepedia.com/' ) ) {
 						if ( $('.mw-blocklist').length ) {
 							return Promise.reject({
-								desc: lang.get('verify.user_gblocked').replaceSave( '%s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ),
-								reply: lang.get('verify.user_gblocked_reply').replaceSave( '%s', username.escapeFormatting() )
+								desc: lang.get('verify.user_gblocked', '[' + username.escapeFormatting() + '](' + pagelink + ')'),
+								reply: lang.get('verify.user_gblocked_reply', username.escapeFormatting())
 							});
 						}
 					}
 					else if ( wiki.isFandom() ) {
 						if ( $('#mw-content-text .errorbox').length ) {
 							return Promise.reject({
-								desc: lang.get('verify.user_disabled').replaceSave( '%s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ),
-								reply: lang.get('verify.user_disabled_reply').replaceSave( '%s', username.escapeFormatting() )
+								desc: lang.get('verify.user_disabled', '[' + username.escapeFormatting() + '](' + pagelink + ')'),
+								reply: lang.get('verify.user_disabled_reply', username.escapeFormatting())
 							});
 						}
 						else if ( $('.mw-warning-with-logexcerpt').length && !$(".mw-warning-with-logexcerpt .mw-logline-block").length ) {
 							return Promise.reject({
-								desc: lang.get('verify.user_gblocked').replaceSave( '%s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ),
-								reply: lang.get('verify.user_gblocked_reply').replaceSave( '%s', username.escapeFormatting() )
+								desc: lang.get('verify.user_gblocked', '[' + username.escapeFormatting() + '](' + pagelink + ')'),
+								reply: lang.get('verify.user_gblocked_reply', username.escapeFormatting())
 							});
 						}
 					}
@@ -156,12 +156,12 @@ function cmd_verify(lang, msg, args, line, wiki) {
 					if ( discordname.length > 50 ) discordname = discordname.substring(0, 50) + '\u2026';
 					embed.addField( lang.get('verify.discord'), msg.author.tag.escapeFormatting(), true ).addField( lang.get('verify.wiki'), ( discordname || lang.get('verify.empty') ), true );
 					if ( msg.author.tag.escapeFormatting() !== discordname ) {
-						embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed').replaceSave( '%1$s', msg.member.toString() ).replaceSave( '%2$s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ) );
+						embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed', msg.member.toString(), '[' + username.escapeFormatting() + '](' + pagelink + ')') );
 						var help_link = '';
 						if ( wiki.endsWith( '.gamepedia.com/' ) ) help_link = lang.get('verify.help_gamepedia') + '?c=' + ( msg.guild.id in patreons && patreons[msg.guild.id] !== process.env.prefix ? encodeURIComponent( patreons[msg.guild.id] + ' verify' ) : 'wb' ) + ( msg.channel.name !== 'verification' ? '&ch=' + encodeURIComponent( msg.channel.name ) : '' ) + '&user=' + username.toTitle(true, true) + '&discord=' + encodeURIComponent( msg.author.username ) + '&tag=' + msg.author.discriminator;
 						else if ( wiki.isFandom() ) help_link = lang.get('verify.help_fandom') + '/' + username.toTitle(true) + '?c=' + ( msg.guild.id in patreons && patreons[msg.guild.id] !== process.env.prefix ? encodeURIComponent( patreons[msg.guild.id] + ' verify' ) : 'wb' ) + ( msg.channel.name !== 'verification' ? '&ch=' + encodeURIComponent( msg.channel.name ) : '' ) + '&user=' + encodeURIComponent( msg.author.username ) + '&tag=' + msg.author.discriminator;
-						if ( help_link.length ) embed.addField( lang.get('verify.notice'), lang.get('verify.help_guide').replaceSave( '%s', help_link ) + '\n' + help_link );
-						msg.replyMsg( lang.get('verify.user_failed_reply').replaceSave( '%s', username.escapeFormatting() ), {embed}, false, false );
+						if ( help_link.length ) embed.addField( lang.get('verify.notice'), lang.get('verify.help_guide', help_link) + '\n' + help_link );
+						msg.replyMsg( lang.get('verify.user_failed_reply', username.escapeFormatting()), {embed}, false, false );
 						
 						if ( reaction ) reaction.removeEmoji();
 						return;
@@ -196,16 +196,18 @@ function cmd_verify(lang, msg, args, line, wiki) {
 						}
 					} );
 					if ( verified ) {
-						embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified').replaceSave( '%1$s', msg.member.toString() ).replaceSave( '%2$s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ) + ( rename ? '\n' + lang.get('verify.user_renamed') : '' ) );
-						var text = lang.get('verify.user_verified_reply').replaceSave( '%s', username.escapeFormatting() );
+						embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', msg.member.toString(), '[' + username.escapeFormatting() + '](' + pagelink + ')') + ( rename ? '\n' + lang.get('verify.user_renamed') : '' ) );
+						var text = lang.get('verify.user_verified_reply', username.escapeFormatting());
 						var verify_promise = [
-							msg.member.roles.add( roles, lang.get('verify.audit_reason').replaceSave( '%s', username ) ).catch( error => {
+							msg.member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
+								log_error(error);
 								embed.setColor('#008800');
 								comment.push(lang.get('verify.failed_roles'));
 							} )
 						];
 						if ( rename ) {
-							verify_promise.push(msg.member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason').replaceSave( '%s', username ) ).catch( error => {
+							verify_promise.push(msg.member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
+								log_error(error);
 								embed.setColor('#008800');
 								comment.push(lang.get('verify.failed_rename'));
 							} ));
@@ -227,8 +229,8 @@ function cmd_verify(lang, msg, args, line, wiki) {
 						} );
 					}
 					
-					embed.setColor('#FFFF00').setDescription( lang.get('verify.user_matches').replaceSave( '%1$s', msg.member.toString() ).replaceSave( '%2$s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ) );
-					msg.replyMsg( lang.get('verify.user_matches_reply').replaceSave( '%s', username.escapeFormatting() ), {embed}, false, false );
+					embed.setColor('#FFFF00').setDescription( lang.get('verify.user_matches', msg.member.toString(), '[' + username.escapeFormatting() + '](' + pagelink + ')') );
+					msg.replyMsg( lang.get('verify.user_matches_reply', username.escapeFormatting()), {embed}, false, false );
 					
 					if ( reaction ) reaction.removeEmoji();
 				}, error => {
@@ -265,9 +267,9 @@ function cmd_verify(lang, msg, args, line, wiki) {
 				if ( discordname.length > 50 ) discordname = discordname.substring(0, 50) + '\u2026';
 				embed.addField( lang.get('verify.discord'), msg.author.tag.escapeFormatting(), true ).addField( lang.get('verify.wiki'), ( discordname || lang.get('verify.empty') ), true );
 				if ( msg.author.tag.escapeFormatting() !== discordname ) {
-					embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed').replaceSave( '%1$s', msg.member.toString() ).replaceSave( '%2$s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ) );
-					embed.addField( lang.get('verify.notice'), lang.get('verify.help_subpage').replaceSave( '%s', '**`' + msg.author.tag + '`**' ) + '\n' + wiki.toLink('Special:MyPage/Discord', 'action=edit', '', body.query.general) );
-					msg.replyMsg( lang.get('verify.user_failed_reply').replaceSave( '%s', username.escapeFormatting() ), {embed}, false, false );
+					embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed', msg.member.toString(), '[' + username.escapeFormatting() + '](' + pagelink + ')') );
+					embed.addField( lang.get('verify.notice'), lang.get('verify.help_subpage', '**`' + msg.author.tag + '`**') + '\n' + wiki.toLink('Special:MyPage/Discord', 'action=edit', '', body.query.general) );
+					msg.replyMsg( lang.get('verify.user_failed_reply', username.escapeFormatting()), {embed}, false, false );
 					
 					if ( reaction ) reaction.removeEmoji();
 					return;
@@ -302,16 +304,18 @@ function cmd_verify(lang, msg, args, line, wiki) {
 					}
 				} );
 				if ( verified ) {
-					embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified').replaceSave( '%1$s', msg.member.toString() ).replaceSave( '%2$s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ) + ( rename ? '\n' + lang.get('verify.user_renamed') : '' ) );
-					var text = lang.get('verify.user_verified_reply').replaceSave( '%s', username.escapeFormatting() );
+					embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', msg.member.toString(), '[' + username.escapeFormatting() + '](' + pagelink + ')') + ( rename ? '\n' + lang.get('verify.user_renamed') : '' ) );
+					var text = lang.get('verify.user_verified_reply', username.escapeFormatting());
 					var verify_promise = [
-						msg.member.roles.add( roles, lang.get('verify.audit_reason').replaceSave( '%s', username ) ).catch( error => {
+						msg.member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
+							log_error(error);
 							embed.setColor('#008800');
 							comment.push(lang.get('verify.failed_roles'));
 						} )
 					];
 					if ( rename ) {
-						verify_promise.push(msg.member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason').replaceSave( '%s', username ) ).catch( error => {
+						verify_promise.push(msg.member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
+							log_error(error);
 							embed.setColor('#008800');
 							comment.push(lang.get('verify.failed_rename'));
 						} ));
@@ -333,8 +337,8 @@ function cmd_verify(lang, msg, args, line, wiki) {
 					} );
 				}
 				
-				embed.setColor('#FFFF00').setDescription( lang.get('verify.user_matches').replaceSave( '%1$s', msg.member.toString() ).replaceSave( '%2$s', '[' + username.escapeFormatting() + '](' + pagelink + ')' ) );
-				msg.replyMsg( lang.get('verify.user_matches_reply').replaceSave( '%s', username.escapeFormatting() ), {embed}, false, false );
+				embed.setColor('#FFFF00').setDescription( lang.get('verify.user_matches', msg.member.toString(), '[' + username.escapeFormatting() + '](' + pagelink + ')') );
+				msg.replyMsg( lang.get('verify.user_matches_reply', username.escapeFormatting()), {embed}, false, false );
 				
 				if ( reaction ) reaction.removeEmoji();
 			}, error => {

+ 1 - 1
cmds/voice.js

@@ -5,7 +5,7 @@ function cmd_voice(lang, msg, args, line, wiki) {
 	if ( msg.isAdmin() ) {
 		if ( !args.join('') ) {
 			var text = lang.get('voice.text') + '\n`' + lang.get('voice.channel') + ' – <' + lang.get('voice.name') + '>`\n';
-			text += lang.get('voice.' + ( msg.guild.id in voice ? 'disable' : 'enable' )).replaceSave( '%s', ( patreons[msg.guild.id] || process.env.prefix ) + 'voice toggle' );
+			text += lang.get('voice.' + ( msg.guild.id in voice ? 'disable' : 'enable' ), ( patreons[msg.guild.id] || process.env.prefix ) + 'voice toggle');
 			return msg.replyMsg( text, {}, true );
 		}
 		args[1] = args.slice(1).join(' ').trim()

+ 58 - 80
cmds/wiki/fandom.js

@@ -1,6 +1,5 @@
 const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
-const Lang = require('../../util/i18n.js');
 
 const fs = require('fs');
 var fn = {
@@ -15,7 +14,7 @@ fs.readdir( './cmds/wiki/fandom', (error, files) => {
 	} );
 } );
 
-function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '', querystring = '', fragment = '', selfcall = 0) {
+function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '', querystring = '', fragment = '', interwiki = '', selfcall = 0) {
 	var full_title = title;
 	if ( title.includes( '#' ) ) {
 		fragment = title.split('#').slice(1).join('#');
@@ -43,13 +42,14 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 	else if ( aliasInvoke === 'diff' && args.join('') && !querystring && !fragment ) fn.diff(lang, msg, args, wiki, reaction, spoiler);
 	else {
 		var noRedirect = ( /(?:^|&)redirect=no(?:&|$)/.test(querystring) || /(?:^|&)action=(?!view(?:&|$))/.test(querystring) );
-		got.get( wiki + 'api.php?action=query&meta=allmessages|siteinfo&ammessages=description&amenableparser=true&siprop=general|namespaces|specialpagealiases|wikidesc&iwurl=true' + ( noRedirect ? '' : '&redirects=true' ) + '&prop=imageinfo|categoryinfo&titles=' + encodeURIComponent( title ) + '&format=json', {
+		got.get( wiki + 'api.php?action=query&meta=allmessages|siteinfo&ammessages=description&amenableparser=true&siprop=general|namespaces|specialpagealiases|wikidesc&iwurl=true' + ( noRedirect ? '' : '&redirects=true' ) + '&prop=imageinfo|categoryinfo&titles=%1F' + encodeURIComponent( title.replace( /\x1F/g, '\ufffd' ) ) + '&format=json', {
 			responseType: 'json'
 		} ).then( response => {
 			var body = response.body;
 			if ( body && body.warnings ) log_warn(body.warnings);
 			if ( response.statusCode !== 200 || !body || !body.query ) {
-				if ( wiki.noWiki(response.url) || response.statusCode === 410 ) {
+				if ( interwiki ) msg.sendChannel( spoiler + ' ' + interwiki.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler );
+				else if ( wiki.noWiki(response.url) || response.statusCode === 410 ) {
 					console.log( '- This wiki doesn\'t exist!' );
 					msg.reactEmoji('nowiki');
 				}
@@ -80,11 +80,6 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 						querypage.ns = -1;
 						querypage.special = '';
 					}
-					if ( querypages.length !== 1 ) querypage = {
-						title: title,
-						invalidreason: 'The requested page title contains invalid characters: "|".',
-						invalid: ''
-					}
 					
 					var contribs = body.query.namespaces['-1']['*'] + ':' + body.query.specialpagealiases.find( sp => sp.realname === 'Contributions' ).aliases[0] + '/';
 					if ( querypage.ns === 2 && ( !querypage.title.includes( '/' ) || /^[^:]+:(?:(?:\d{1,3}\.){3}\d{1,3}\/\d{2}|(?:[\dA-F]{1,4}:){7}[\dA-F]{1,4}\/\d{2,3})$/.test(querypage.title) ) ) {
@@ -215,10 +210,10 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 									text = '';
 								}
 								else if ( wsbody.total === 1 ) {
-									text = '\n' + lang.get('search.infopage').replaceSave( '%s', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`' );
+									text = '\n' + lang.get('search.infopage', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`');
 								}
 								else {
-									text = '\n' + lang.get('search.infosearch').replaceSave( '%1$s', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`' ).replaceSave( '%2$s', '`' + prefix + cmd + lang.get('search.search') + ' ' + title + linksuffix + '`' );
+									text = '\n' + lang.get('search.infosearch', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`', '`' + prefix + cmd + lang.get('search.search') + ' ' + title + linksuffix + '`');
 								}
 								got.get( wiki + 'api.php?action=query&prop=imageinfo|categoryinfo&titles=' + encodeURIComponent( querypage.title ) + '&format=json', {
 									responseType: 'json'
@@ -242,23 +237,18 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 											else if ( msg.uploadFiles() ) embed.attachFiles( [{attachment:pageimage,name:( spoiler ? 'SPOILER ' : '' ) + filename}] );
 										}
 										if ( querypage.categoryinfo ) {
-											var langCat = new Lang(lang.lang, 'search.category');
-											var category = [langCat.get('content')];
-											if ( querypage.categoryinfo.size === 0 ) category.push(langCat.get('empty'));
+											var category = [lang.get('search.category.content')];
+											if ( querypage.categoryinfo.size === 0 ) {
+												category.push(lang.get('search.category.empty'));
+											}
 											if ( querypage.categoryinfo.pages > 0 ) {
-												let pages = querypage.categoryinfo.pages;
-												let count = langCat.get('pages');
-												category.push(( count[pages] || count['*' + pages % 100] || count['*' + pages % 10] || langCat.get('pages.default') ).replaceSave( '%s', pages ));
+												category.push(lang.get('search.category.pages', querypage.categoryinfo.pages));
 											}
 											if ( querypage.categoryinfo.files > 0 ) {
-												let files = querypage.categoryinfo.files;
-												let count = langCat.get('files');
-												category.push(( count[files] || count['*' + files % 100] || count['*' + files % 10] || langCat.get('files.default') ).replaceSave( '%s', files ));
+												category.push(lang.get('search.category.files', querypage.categoryinfo.files));
 											}
 											if ( querypage.categoryinfo.subcats > 0 ) {
-												let subcats = querypage.categoryinfo.subcats;
-												let count = langCat.get('subcats');
-												category.push(( count[subcats] || count['*' + subcats % 100] || count['*' + subcats % 10] || langCat.get('subcats.default') ).replaceSave( '%s', subcats ));
+												category.push(lang.get('search.category.subcats', querypage.categoryinfo.subcats));
 											}
 											if ( msg.showEmbed() ) embed.addField( category[0], category.slice(1).join('\n') );
 											else text += '\n\n' + category.join('\n');
@@ -334,23 +324,18 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 							else if ( msg.uploadFiles() ) embed.attachFiles( [{attachment:pageimage,name:( spoiler ? 'SPOILER ' : '' ) + filename}] );
 						}
 						if ( querypage.categoryinfo ) {
-							var langCat = new Lang(lang.lang, 'search.category');
-							var category = [langCat.get('content')];
-							if ( querypage.categoryinfo.size === 0 ) category.push(langCat.get('empty'));
+							var category = [lang.get('search.category.content')];
+							if ( querypage.categoryinfo.size === 0 ) {
+								category.push(lang.get('search.category.empty'));
+							}
 							if ( querypage.categoryinfo.pages > 0 ) {
-								let pages = querypage.categoryinfo.pages;
-								let count = langCat.get('pages');
-								category.push(( count[pages] || count['*' + pages % 100] || count['*' + pages % 10]  || langCat.get('pages.default') ).replaceSave( '%s', pages ));
+								category.push(lang.get('search.category.pages', querypage.categoryinfo.pages));
 							}
 							if ( querypage.categoryinfo.files > 0 ) {
-								let files = querypage.categoryinfo.files;
-								let count = langCat.get('files');
-								category.push(( count[files] || count['*' + files % 100] || count['*' + files % 10]  || langCat.get('files.default') ).replaceSave( '%s', files ));
+								category.push(lang.get('search.category.files', querypage.categoryinfo.files));
 							}
 							if ( querypage.categoryinfo.subcats > 0 ) {
-								let subcats = querypage.categoryinfo.subcats;
-								let count = langCat.get('subcats');
-								category.push(( count[subcats] || count['*' + subcats % 100] || count['*' + subcats % 10]  || langCat.get('subcats.default') ).replaceSave( '%s', subcats ));
+								category.push(lang.get('search.category.subcats', querypage.categoryinfo.subcats));
 							}
 							if ( msg.showEmbed() ) embed.addField( category[0], category.slice(1).join('\n') );
 							else text += '\n\n' + category.join('\n');
@@ -396,55 +381,47 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 					}
 				}
 				else if ( body.query.interwiki ) {
-					var inter = body.query.interwiki[0];
-					var intertitle = inter.title.substring(inter.iw.length + 1);
-					var regex = inter.url.match( /^(?:https?:)?\/\/(([a-z\d-]{1,50})\.(?:fandom\.com|wikia\.org)(?:(?!\/wiki\/)\/([a-z-]{2,8}))?)(?:\/wiki\/|\/?$)/ );
+					if ( msg.channel.type === 'text' && pause[msg.guild.id] ) {
+						if ( reaction ) reaction.removeEmoji();
+						console.log( '- Aborted, paused.' );
+						return;
+					}
+					interwiki = body.query.interwiki[0].url;
 					var maxselfcall = ( msg.channel.type === 'text' && msg.guild.id in patreons ? 10 : 5 );
-					if ( regex !== null && selfcall < maxselfcall ) {
-						if ( msg.channel.type !== 'text' || !pause[msg.guild.id] ) {
-							var iwtitle = decodeURIComponent( inter.url.replace( regex[0], '' ) ).replace( /\_/g, ' ' ).replaceSave( intertitle.replace( /\_/g, ' ' ), intertitle );
-							selfcall++;
-							this.fandom(lang, msg, iwtitle, 'https://' + regex[1] + '/', '?' + ( regex[3] ? regex[3] + '.' : '' ) + regex[2] + ' ', reaction, spoiler, querystring, fragment, selfcall);
-						} else {
-							if ( reaction ) reaction.removeEmoji();
-							console.log( '- Aborted, paused.' );
+					if ( selfcall < maxselfcall ) {
+						selfcall++;
+						var regex = interwiki.match( /^(?:https?:)?\/\/(([a-z\d-]{1,50})\.(?:fandom\.com|wikia\.org)(?:(?!\/wiki\/)\/([a-z-]{2,12}))?)(?:\/wiki\/|\/?$)/ );
+						if ( regex ) {
+							let iwtitle = decodeURIComponent( interwiki.replace( regex[0], '' ) ).replace( /\_/g, ' ' );
+							this.fandom(lang, msg, iwtitle, 'https://' + regex[1] + '/', ( regex[1].includes( '.wikia.org' ) ? '??' : '?' ) + ( regex[3] ? regex[3] + '.' : '' ) + regex[2] + ' ', reaction, spoiler, querystring, fragment, interwiki, selfcall);
+							return;
 						}
-					} else {
-						regex = inter.url.match( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.gamepedia\.com(?:\/|$)/ );
-						if ( regex !== null && selfcall < maxselfcall ) {
-							if ( msg.channel.type !== 'text' || !pause[msg.guild.id] ) {
-								var iwtitle = decodeURIComponent( inter.url.replace( regex[0], '' ) ).replace( /\_/g, ' ' ).replaceSave( intertitle.replace( /\_/g, ' ' ), intertitle );
-								selfcall++;
-								this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + '.gamepedia.com/', '!' + regex[1] + ' ', reaction, spoiler, querystring, fragment, selfcall);
-							} else {
-								if ( reaction ) reaction.removeEmoji();
-								console.log( '- Aborted, paused.' );
-							}
-						} else {
-							regex = inter.url.match( /^(?:https?:)?\/\/([a-z\d-]{1,50}\.(?:wikipedia|mediawiki|wiktionary|wikimedia|wikibooks|wikisource|wikidata|wikiversity|wikiquote|wikinews|wikivoyage)\.org)(?:\/wiki\/|\/?$)/ );
-							if ( regex !== null && selfcall < maxselfcall ) {
-								if ( msg.channel.type !== 'text' || !pause[msg.guild.id] ) {
-									var iwtitle = decodeURIComponent( inter.url.replace( regex[0], '' ) ).replace( /\_/g, ' ' ).replaceSave( intertitle.replace( /\_/g, ' ' ), intertitle );
-									selfcall++;
-									this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + '/w/', cmd + inter.iw + ':', reaction, spoiler, querystring, fragment, selfcall);
-								} else {
-									if ( reaction ) reaction.removeEmoji();
-									console.log( '- Aborted, paused.' );
-								}
-							} else {
-								if ( fragment ) fragment = '#' + fragment.toSection();
-								if ( inter.url.includes( '#' ) ) {
-									if ( !fragment ) fragment = '#' + inter.url.split('#').slice(1).join('#');
-									inter.url = inter.url.split('#')[0];
-								}
-								if ( querystring ) inter.url += ( inter.url.includes( '?' ) ? '&' : '?' ) + querystring.toTitle();
-								msg.sendChannel( spoiler + ' ' + inter.url.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler ).then( message => {
-									if ( message && selfcall === maxselfcall ) message.reactEmoji('⚠️');
-								} );
-								if ( reaction ) reaction.removeEmoji();
+						regex = interwiki.match( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.gamepedia\.com(?:\/|$)/ );
+						if ( regex ) {
+							let iwtitle = decodeURIComponent( interwiki.replace( regex[0], '' ) ).replace( /\_/g, ' ' );
+							this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + '.gamepedia.com/', '!' + regex[1] + ' ', reaction, spoiler, querystring, fragment, interwiki, selfcall);
+							return;
+						}
+						if ( wikiProjects.some( project => interwiki.includes( project.name ) ) ) {
+							let project = wikiProjects.find( project => interwiki.includes( project.name ) );
+							regex = interwiki.match( new RegExp( '^(?:https?:)?\\/\\/' + project.regex ) );
+							if ( regex ) {
+								let iwtitle = decodeURIComponent( interwiki.replace( regex[0], '' ) ).replace( /\_/g, ' ' );
+								this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + project.scriptPath, cmd + body.query.interwiki[0].iw + ':', reaction, spoiler, querystring, fragment, interwiki, selfcall);
+								return;
 							}
 						}
 					}
+					if ( fragment ) fragment = '#' + fragment.toSection();
+					if ( interwiki.includes( '#' ) ) {
+						if ( !fragment ) fragment = '#' + interwiki.split('#').slice(1).join('#');
+						interwiki = interwiki.split('#')[0];
+					}
+					if ( querystring ) interwiki += ( interwiki.includes( '?' ) ? '&' : '?' ) + querystring.toTitle();
+					msg.sendChannel( spoiler + ' ' + interwiki.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler ).then( message => {
+						if ( message && selfcall === maxselfcall ) message.reactEmoji('⚠️');
+					} );
+					if ( reaction ) reaction.removeEmoji();
 				}
 				else if ( body.query.redirects ) {
 					var pagelink = wiki.toLink(body.query.redirects[0].to, querystring.toTitle(), ( fragment || body.query.redirects[0].tofragment || '' ) );
@@ -492,7 +469,8 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 				}
 			}
 		}, error => {
-			if ( wiki.noWiki(error.message) ) {
+			if ( interwiki ) msg.sendChannel( spoiler + ' ' + interwiki.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler );
+			else if ( wiki.noWiki(error.message) ) {
 				console.log( '- This wiki doesn\'t exist!' );
 				msg.reactEmoji('nowiki');
 			}

+ 1 - 1
cmds/wiki/fandom/diff.js

@@ -228,7 +228,7 @@ function fandom_diff_send(lang, msg, args, wiki, reaction, spoiler, compare) {
 					var editor = [lang.get('diff.info.editor'), ( revisions[0].userhidden !== undefined ? lang.get('diff.hidden') : revisions[0].user )];
 					var timestamp = [lang.get('diff.info.timestamp'), new Date(revisions[0].timestamp).toLocaleString(lang.get('dateformat'), timeoptions)];
 					var difference = revisions[0].size - ( revisions[1] ? revisions[1].size : 0 );
-					var size = [lang.get('diff.info.size'), lang.get('diff.info.bytes').replace( '%s', ( difference > 0 ? '+' : '' ) + difference )];
+					var size = [lang.get('diff.info.size'), lang.get('diff.info.bytes', ( difference > 0 ? '+' : '' ) + difference)];
 					var comment = [lang.get('diff.info.comment'), ( revisions[0].commenthidden !== undefined ? lang.get('diff.hidden') : ( revisions[0].comment ? revisions[0].comment.toFormatting(msg.showEmbed(), wiki, body.query.general, title) : lang.get('diff.nocomment') ) )];
 					if ( revisions[0].tags.length ) var tags = [lang.get('diff.info.tags'), body.query.tags.filter( tag => revisions[0].tags.includes( tag.name ) ).map( tag => tag.displayname ).join(', ')];
 					

+ 1 - 2
cmds/wiki/fandom/search.js

@@ -29,8 +29,7 @@ function fandom_search(lang, msg, searchterm, wiki, query, reaction, spoiler) {
 		body.items.forEach( result => {
 			description.push( '• [' + result.title + '](' + wiki.toLink(result.title, '', '', query.general, true) + ')' );
 		} );
-		let count = lang.get('search.results');
-		embed.setFooter( ( count[body.total] || count['*' + body.total % 100] || count['*' + body.total % 10]  || lang.get('search.results.default') ).replaceSave( '%s', body.total ) );
+		embed.setFooter( lang.get('search.results', body.total) );
 	}, error => {
 		console.log( '- Error while getting the search results.' + error );
 	} ).finally( () => {

+ 12 - 12
cmds/wiki/fandom/user.js

@@ -72,8 +72,8 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 						blockexpiry = new Date(blockexpiry).toLocaleString(lang.get('dateformat'), timeoptions);
 					}
 					if ( isBlocked ) return {
-						header: lang.get('user.block.header').replaceSave( '%s', block.user ).escapeFormatting(),
-						text: lang.get('user.block.' + ( block.reason ? 'text' : 'noreason' )).replaceSave( '%1$s', blockedtimestamp ).replaceSave( '%2$s', blockexpiry ),
+						header: lang.get('user.block.header', block.user).escapeFormatting(),
+						text: lang.get('user.block.' + ( block.reason ? 'text' : 'noreason' ), blockedtimestamp, blockexpiry),
 						by: block.by,
 						reason: block.reason
 					};
@@ -121,8 +121,8 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 							var text = '<' + pagelink + '>';
 							var embed = new MessageEmbed().setAuthor( body.query.general.sitename ).setTitle( username ).setURL( pagelink ).addField( editcount[0], '[' + editcount[1] + '](' + wiki.toLink(contribs + username, '', '', body.query.general, true) + ')' );
 							if ( blocks.length ) {
-								block.text = block.text.replaceSave( '%3$s', '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toMarkdown(wiki, body.query.general) );
+								block.text = block.text.replaceSave( /\$3/g, '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toMarkdown(wiki, body.query.general) );
 								embed.addField( block.header, block.text );
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) embed.addField( '\u200b', '<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**' );
@@ -131,8 +131,8 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 							var embed = {};
 							var text = '<' + pagelink + '>\n\n' + editcount.join(' ');
 							if ( blocks.length ) {
-								block.text = block.text.replaceSave( '%3$s', block.by.escapeFormatting() );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toPlaintext() );
+								block.text = block.text.replaceSave( /\$3/g, block.by.escapeFormatting() );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toPlaintext() );
 								text += '\n\n**' + block.header + '**\n' + block.text;
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) text += '\n\n<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**';
@@ -246,8 +246,8 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 					var blockedby = '[[User:' + queryuser.blockedby + '|' + queryuser.blockedby + ']]';
 					var blockreason = queryuser.blockreason;
 					var block = {
-						header: lang.get('user.block.header').replaceSave( '%s', username ).escapeFormatting(),
-						text: lang.get('user.block.nofrom' + ( blockreason ? 'text' : 'noreason' )).replaceSave( '%2$s', blockexpiry ),
+						header: lang.get('user.block.header', username).escapeFormatting(),
+						text: lang.get('user.block.nofrom' + ( blockreason ? 'text' : 'noreason' ), '', blockexpiry ),
 						by: blockedby,
 						reason: blockreason
 					};
@@ -305,16 +305,16 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 					} ).finally( () => {
 						if ( msg.showEmbed() ) {
 							if ( isBlocked ) {
-								block.text = block.text.replaceSave( '%3$s', '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toMarkdown(wiki, body.query.general) );
+								block.text = block.text.replaceSave( /\$3/g, '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toMarkdown(wiki, body.query.general) );
 								embed.addField( block.header, block.text );
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) embed.addField( '\u200b', '<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**' );
 						}
 						else {
 							if ( isBlocked ) {
-								block.text = block.text.replaceSave( '%3$s', block.by.escapeFormatting() );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toPlaintext() );
+								block.text = block.text.replaceSave( /\$3/g, block.by.escapeFormatting() );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toPlaintext() );
 								text += '\n\n**' + block.header + '**\n' + block.text;
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) text += '\n\n<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**';

+ 58 - 80
cmds/wiki/gamepedia.js

@@ -1,6 +1,5 @@
 const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
-const Lang = require('../../util/i18n.js');
 const extract_desc = require('../../util/extract_desc.js');
 
 const fs = require('fs');
@@ -24,7 +23,7 @@ fs.readdir( './cmds/minecraft', (error, files) => {
 	} );
 } );
 
-function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '', querystring = '', fragment = '', selfcall = 0) {
+function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '', querystring = '', fragment = '', interwiki = '', selfcall = 0) {
 	var full_title = title;
 	if ( title.includes( '#' ) ) {
 		fragment = title.split('#').slice(1).join('#');
@@ -58,13 +57,14 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 	else if ( aliasInvoke === 'diff' && args.join('') && !querystring && !fragment ) fn.diff(lang, msg, args, wiki, reaction, spoiler);
 	else {
 		var noRedirect = ( /(?:^|&)redirect=no(?:&|$)/.test(querystring) || /(?:^|&)action=(?!view(?:&|$))/.test(querystring) );
-		got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|specialpagealiases&iwurl=true' + ( noRedirect ? '' : '&redirects=true' ) + '&prop=pageimages|categoryinfo|pageprops|extracts&piprop=original|name&ppprop=description|displaytitle&explaintext=true&exsectionformat=raw&exlimit=1&titles=' + encodeURIComponent( title ) + '&format=json', {
+		got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|specialpagealiases&iwurl=true' + ( noRedirect ? '' : '&redirects=true' ) + '&prop=pageimages|categoryinfo|pageprops|extracts&piprop=original|name&ppprop=description|displaytitle&explaintext=true&exsectionformat=raw&exlimit=1&titles=%1F' + encodeURIComponent( title.replace( /\x1F/g, '\ufffd' ) ) + '&format=json', {
 			responseType: 'json'
 		} ).then( response => {
 			var body = response.body;
 			if ( body && body.warnings ) log_warn(body.warnings);
 			if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !body.query ) {
-				if ( wiki.noWiki(response.url) || response.statusCode === 410 ) {
+				if ( interwiki ) msg.sendChannel( spoiler + ' ' + interwiki.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler );
+				else if ( wiki.noWiki(response.url) || response.statusCode === 410 ) {
 					console.log( '- This wiki doesn\'t exist!' );
 					msg.reactEmoji('nowiki');
 				}
@@ -92,11 +92,6 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 						querypage.ns = -1;
 						querypage.special = '';
 					}
-					if ( querypages.length !== 1 ) querypage = {
-						title: title,
-						invalidreason: 'The requested page title contains invalid characters: "|".',
-						invalid: ''
-					}
 					
 					var contribs = body.query.namespaces['-1']['*'] + ':' + body.query.specialpagealiases.find( sp => sp.realname === 'Contributions' ).aliases[0] + '/';
 					if ( ( querypage.ns === 2 || querypage.ns === 202 || querypage.ns === 1200 ) && ( !querypage.title.includes( '/' ) || /^[^:]+:(?:(?:\d{1,3}\.){3}\d{1,3}\/\d{2}|(?:[\dA-F]{1,4}:){7}[\dA-F]{1,4}\/\d{2,3})$/.test(querypage.title) ) ) {
@@ -188,30 +183,25 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 										text = '';
 									}
 									else if ( !srbody.continue ) {
-										text = '\n' + lang.get('search.infopage').replaceSave( '%s', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`' );
+										text = '\n' + lang.get('search.infopage', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`');
 									}
 									else {
-										text = '\n' + lang.get('search.infosearch').replaceSave( '%1$s', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`' ).replaceSave( '%2$s', '`' + prefix + cmd + lang.get('search.search') + ' ' + title + linksuffix + '`' );
+										text = '\n' + lang.get('search.infosearch', '`' + prefix + cmd + lang.get('search.page') + ' ' + title + linksuffix + '`', '`' + prefix + cmd + lang.get('search.search') + ' ' + title + linksuffix + '`');
 									}
 									
 									if ( querypage.categoryinfo ) {
-										var langCat = new Lang(lang.lang, 'search.category');
-										var category = [langCat.get('content')];
-										if ( querypage.categoryinfo.size === 0 ) category.push(langCat.get('empty'));
+										var category = [lang.get('search.category.content')];
+										if ( querypage.categoryinfo.size === 0 ) {
+											category.push(lang.get('search.category.empty'));
+										}
 										if ( querypage.categoryinfo.pages > 0 ) {
-											let pages = querypage.categoryinfo.pages;
-											let count = langCat.get('pages');
-											category.push(( count[pages] || count['*' + pages % 100] || count['*' + pages % 10] || langCat.get('pages.default') ).replaceSave( '%s', pages ));
+											category.push(lang.get('search.category.pages', querypage.categoryinfo.pages));
 										}
 										if ( querypage.categoryinfo.files > 0 ) {
-											let files = querypage.categoryinfo.files;
-											let count = langCat.get('files');
-											category.push(( count[files] || count['*' + files % 100] || count['*' + files % 10] || langCat.get('files.default') ).replaceSave( '%s', files ));
+											category.push(lang.get('search.category.files', querypage.categoryinfo.files));
 										}
 										if ( querypage.categoryinfo.subcats > 0 ) {
-											let subcats = querypage.categoryinfo.subcats;
-											let count = langCat.get('subcats');
-											category.push(( count[subcats] || count['*' + subcats % 100] || count['*' + subcats % 10] || langCat.get('subcats.default') ).replaceSave( '%s', subcats ));
+											category.push(lang.get('search.category.subcats', querypage.categoryinfo.subcats));
 										}
 										if ( msg.showEmbed() ) embed.addField( category[0], category.slice(1).join('\n') );
 										else text += '\n\n' + category.join('\n');
@@ -261,23 +251,18 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 							} else embed.setThumbnail( pageimage );
 						} else embed.setThumbnail( ( body.query.general.logo.startsWith( '//' ) ? 'https:' : '' ) + body.query.general.logo );
 						if ( querypage.categoryinfo ) {
-							var langCat = new Lang(lang.lang, 'search.category');
-							var category = [langCat.get('content')];
-							if ( querypage.categoryinfo.size === 0 ) category.push(langCat.get('empty'));
+							var category = [lang.get('search.category.content')];
+							if ( querypage.categoryinfo.size === 0 ) {
+								category.push(lang.get('search.category.empty'));
+							}
 							if ( querypage.categoryinfo.pages > 0 ) {
-								let pages = querypage.categoryinfo.pages;
-								let count = langCat.get('pages');
-								category.push(( count[pages] || count['*' + pages % 100] || count['*' + pages % 10] || langCat.get('pages.default') ).replaceSave( '%s', pages ));
+								category.push(lang.get('search.category.pages', querypage.categoryinfo.pages));
 							}
 							if ( querypage.categoryinfo.files > 0 ) {
-								let files = querypage.categoryinfo.files;
-								let count = langCat.get('files');
-								category.push(( count[files] || count['*' + files % 100] || count['*' + files % 10] || langCat.get('files.default') ).replaceSave( '%s', files ));
+								category.push(lang.get('search.category.files', querypage.categoryinfo.files));
 							}
 							if ( querypage.categoryinfo.subcats > 0 ) {
-								let subcats = querypage.categoryinfo.subcats;
-								let count = langCat.get('subcats');
-								category.push(( count[subcats] || count['*' + subcats % 100] || count['*' + subcats % 10] || langCat.get('subcats.default') ).replaceSave( '%s', subcats ));
+								category.push(lang.get('search.category.subcats', querypage.categoryinfo.subcats));
 							}
 							if ( msg.showEmbed() ) embed.addField( category[0], category.slice(1).join('\n') );
 							else text += '\n\n' + category.join('\n');
@@ -289,55 +274,47 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 					}
 				}
 				else if ( body.query.interwiki ) {
-					var inter = body.query.interwiki[0];
-					var intertitle = inter.title.substring(inter.iw.length + 1);
-					var regex = inter.url.match( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.gamepedia\.com(?:\/|$)/ );
+					if ( msg.channel.type === 'text' && pause[msg.guild.id] ) {
+						if ( reaction ) reaction.removeEmoji();
+						console.log( '- Aborted, paused.' );
+						return;
+					}
+					interwiki = body.query.interwiki[0].url;
 					var maxselfcall = ( msg.channel.type === 'text' && msg.guild.id in patreons ? 10 : 5 );
-					if ( regex !== null && selfcall < maxselfcall ) {
-						if ( msg.channel.type !== 'text' || !pause[msg.guild.id] ) {
-							var iwtitle = decodeURIComponent( inter.url.replace( regex[0], '' ) ).replace( /\_/g, ' ' ).replaceSave( intertitle.replace( /\_/g, ' ' ), intertitle );
-							selfcall++;
-							this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + '.gamepedia.com/', '!' + regex[1] + ' ', reaction, spoiler, querystring, fragment, selfcall);
-						} else {
-							if ( reaction ) reaction.removeEmoji();
-							console.log( '- Aborted, paused.' );
+					if ( selfcall < maxselfcall ) {
+						selfcall++;
+						var regex = interwiki.match( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.gamepedia\.com(?:\/|$)/ );
+						if ( regex ) {
+							let iwtitle = decodeURIComponent( interwiki.replace( regex[0], '' ) ).replace( /\_/g, ' ' );
+							this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + '.gamepedia.com/', '!' + regex[1] + ' ', reaction, spoiler, querystring, fragment, interwiki, selfcall);
+							return;
 						}
-					} else {
-						regex = inter.url.match( /^(?:https?:)?\/\/(([a-z\d-]{1,50})\.(?:fandom\.com|wikia\.org)(?:(?!\/wiki\/)\/([a-z-]{2,8}))?)(?:\/wiki\/|\/?$)/ );
-						if ( regex !== null && selfcall < maxselfcall ) {
-							if ( msg.channel.type !== 'text' || !pause[msg.guild.id] ) {
-								var iwtitle = decodeURIComponent( inter.url.replace( regex[0], '' ) ).replace( /\_/g, ' ' ).replaceSave( intertitle.replace( /\_/g, ' ' ), intertitle );
-								selfcall++;
-								this.fandom(lang, msg, iwtitle, 'https://' + regex[1] + '/', '?' + ( regex[3] ? regex[3] + '.' : '' ) + regex[2] + ' ', reaction, spoiler, querystring, fragment, selfcall);
-							} else {
-								if ( reaction ) reaction.removeEmoji();
-								console.log( '- Aborted, paused.' );
-							}
-						} else {
-							regex = inter.url.match( /^(?:https?:)?\/\/([a-z\d-]{1,50}\.(?:wikipedia|mediawiki|wiktionary|wikimedia|wikibooks|wikisource|wikidata|wikiversity|wikiquote|wikinews|wikivoyage)\.org)(?:\/wiki\/|\/?$)/ );
-							if ( regex !== null && selfcall < maxselfcall ) {
-								if ( msg.channel.type !== 'text' || !pause[msg.guild.id] ) {
-									var iwtitle = decodeURIComponent( inter.url.replace( regex[0], '' ) ).replace( /\_/g, ' ' ).replaceSave( intertitle.replace( /\_/g, ' ' ), intertitle );
-									selfcall++;
-									this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + '/w/', cmd + inter.iw + ':', reaction, spoiler, querystring, fragment, selfcall);
-								} else {
-									if ( reaction ) reaction.removeEmoji();
-									console.log( '- Aborted, paused.' );
-								}
-							} else {
-								if ( fragment ) fragment = '#' + fragment.toSection();
-								if ( inter.url.includes( '#' ) ) {
-									if ( !fragment ) fragment = '#' + inter.url.split('#').slice(1).join('#');
-									inter.url = inter.url.split('#')[0];
-								}
-								if ( querystring ) inter.url += ( inter.url.includes( '?' ) ? '&' : '?' ) + querystring.toTitle();
-								msg.sendChannel( spoiler + ' ' + inter.url.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler ).then( message => {
-									if ( message && selfcall === maxselfcall ) message.reactEmoji('⚠️');
-								} );
-								if ( reaction ) reaction.removeEmoji();
+						regex = interwiki.match( /^(?:https?:)?\/\/(([a-z\d-]{1,50})\.(?:fandom\.com|wikia\.org)(?:(?!\/wiki\/)\/([a-z-]{2,12}))?)(?:\/wiki\/|\/?$)/ );
+						if ( regex ) {
+							let iwtitle = decodeURIComponent( interwiki.replace( regex[0], '' ) ).replace( /\_/g, ' ' );
+							this.fandom(lang, msg, iwtitle, 'https://' + regex[1] + '/', ( regex[1].includes( '.wikia.org' ) ? '??' : '?' ) + ( regex[3] ? regex[3] + '.' : '' ) + regex[2] + ' ', reaction, spoiler, querystring, fragment, interwiki, selfcall);
+							return;
+						}
+						if ( wikiProjects.some( project => interwiki.includes( project.name ) ) ) {
+							let project = wikiProjects.find( project => interwiki.includes( project.name ) );
+							regex = interwiki.match( new RegExp( '^(?:https?:)?\\/\\/' + project.regex ) );
+							if ( regex ) {
+								let iwtitle = decodeURIComponent( interwiki.replace( regex[0], '' ) ).replace( /\_/g, ' ' );
+								this.gamepedia(lang, msg, iwtitle, 'https://' + regex[1] + project.scriptPath, cmd + body.query.interwiki[0].iw + ':', reaction, spoiler, querystring, fragment, interwiki, selfcall);
+								return;
 							}
 						}
 					}
+					if ( fragment ) fragment = '#' + fragment.toSection();
+					if ( interwiki.includes( '#' ) ) {
+						if ( !fragment ) fragment = '#' + interwiki.split('#').slice(1).join('#');
+						interwiki = interwiki.split('#')[0];
+					}
+					if ( querystring ) interwiki += ( interwiki.includes( '?' ) ? '&' : '?' ) + querystring.toTitle();
+					msg.sendChannel( spoiler + ' ' + interwiki.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler ).then( message => {
+						if ( message && selfcall === maxselfcall ) message.reactEmoji('⚠️');
+					} );
+					if ( reaction ) reaction.removeEmoji();
 				}
 				else if ( body.query.redirects ) {
 					var pagelink = wiki.toLink(body.query.redirects[0].to, querystring.toTitle(), ( fragment || body.query.redirects[0].tofragment || '' ), body.query.general);
@@ -385,7 +362,8 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				}
 			}
 		}, error => {
-			if ( wiki.noWiki(error.message) ) {
+			if ( interwiki ) msg.sendChannel( spoiler + ' ' + interwiki.replace( /@(here|everyone)/g, '%40$1' ) + fragment + ' ' + spoiler );
+			else if ( wiki.noWiki(error.message) ) {
 				console.log( '- This wiki doesn\'t exist!' );
 				msg.reactEmoji('nowiki');
 			}

+ 1 - 1
cmds/wiki/gamepedia/diff.js

@@ -241,7 +241,7 @@ function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, compare)
 					var editor = [lang.get('diff.info.editor'), ( revisions[0].userhidden !== undefined ? lang.get('diff.hidden') : revisions[0].user )];
 					var timestamp = [lang.get('diff.info.timestamp'), new Date(revisions[0].timestamp).toLocaleString(lang.get('dateformat'), timeoptions)];
 					var difference = revisions[0].size - ( revisions[1] ? revisions[1].size : 0 );
-					var size = [lang.get('diff.info.size'), lang.get('diff.info.bytes').replace( '%s', ( difference > 0 ? '+' : '' ) + difference )];
+					var size = [lang.get('diff.info.size'), lang.get('diff.info.bytes', ( difference > 0 ? '+' : '' ) + difference)];
 					var comment = [lang.get('diff.info.comment'), ( revisions[0].commenthidden !== undefined ? lang.get('diff.hidden') : ( revisions[0].comment ? revisions[0].comment.toFormatting(msg.showEmbed(), wiki, body.query.general, title) : lang.get('diff.nocomment') ) )];
 					if ( revisions[0].tags.length ) var tags = [lang.get('diff.info.tags'), body.query.tags.filter( tag => revisions[0].tags.includes( tag.name ) ).map( tag => tag.displayname ).join(', ')];
 					

+ 1 - 1
cmds/wiki/gamepedia/overview.js

@@ -6,7 +6,7 @@ const getAllSites = require('../../../util/allSites.js');
 getAllSites.then( sites => allSites = sites );
 
 function gamepedia_overview(lang, msg, wiki, reaction, spoiler) {
-	if ( !allSites.length ) getAllSites.get().then( sites => allSites = sites );
+	if ( !allSites.length ) getAllSites.update();
 	got.get( wiki + 'api.php?action=query&meta=allmessages|siteinfo&ammessages=custom-Wiki_Manager|custom-GamepediaNotice|custom-FandomMergeNotice&amenableparser=true&siprop=general|statistics&titles=Special:Statistics&format=json', {
 		responseType: 'json'
 	} ).then( response => {

+ 1 - 2
cmds/wiki/gamepedia/search.js

@@ -34,8 +34,7 @@ function gamepedia_search(lang, msg, searchterm, wiki, query, reaction, spoiler)
 			body.query.search.forEach( result => {
 				description.push( '• [' + result.title + '](' + wiki.toLink(result.title, '', '', query.general, true) + ')' + ( result.sectiontitle ? ' § [' + result.sectiontitle + '](' + wiki.toLink(result.title, '', result.sectiontitle, query.general, true) + ')' : '' ) + ( result.redirecttitle ? ' (⤷ [' + result.redirecttitle + '](' + wiki.toLink(result.redirecttitle, '', '', query.general, true) + '))' : '' ) );
 			} );
-			let count = lang.get('search.results');
-			embed.setFooter( ( count[body.query.searchinfo.totalhits] || count['*' + body.query.searchinfo.totalhits % 100] || count['*' + body.query.searchinfo.totalhits % 10]  || lang.get('search.results.default') ).replaceSave( '%s', body.query.searchinfo.totalhits ) );
+			embed.setFooter( lang.get('search.results', body.query.searchinfo.totalhits) );
 		}
 	}, error => {
 		console.log( '- Error while getting the search results.' + error );

+ 21 - 21
cmds/wiki/gamepedia/user.js

@@ -9,7 +9,7 @@ const getAllSites = require('../../../util/allSites.js');
 getAllSites.then( sites => allSites = sites );
 
 function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragment, querypage, contribs, reaction, spoiler) {
-	if ( !allSites.length ) getAllSites.get().then( sites => allSites = sites );
+	if ( !allSites.length ) getAllSites.update();
 	if ( /^(?:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{2})?|(?:[\dA-F]{1,4}:){7}[\dA-F]{1,4}(?:\/\d{2,3})?)$/.test(username) ) {
 		got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&list=blocks&bkprop=user|by|timestamp|expiry|reason&bkip=' + encodeURIComponent( username ) + '&format=json', {
 			responseType: 'json'
@@ -66,8 +66,8 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 						blockexpiry = new Date(blockexpiry).toLocaleString(lang.get('dateformat'), timeoptions);
 					}
 					if ( isBlocked ) return {
-						header: lang.get('user.block.header').replaceSave( '%s', block.user ).escapeFormatting(),
-						text: lang.get('user.block.' + ( block.reason ? 'text' : 'noreason' )).replaceSave( '%1$s', blockedtimestamp ).replaceSave( '%2$s', blockexpiry ),
+						header: lang.get('user.block.header', block.user).escapeFormatting(),
+						text: lang.get('user.block.' + ( block.reason ? 'text' : 'noreason' ), blockedtimestamp, blockexpiry),
 						by: block.by,
 						reason: block.reason
 					};
@@ -124,8 +124,8 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 								embed.setDescription( extract[0] );
 							}
 							if ( blocks.length ) blocks.forEach( block => {
-								block.text = block.text.replaceSave( '%3$s', '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toMarkdown(wiki, body.query.general) );
+								block.text = block.text.replaceSave( /\$3/g, '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toMarkdown(wiki, body.query.general) );
 								embed.addField( block.header, block.text );
 							} );
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) embed.addField( '\u200b', '<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**' );
@@ -134,8 +134,8 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 							var embed = {};
 							var text = '<' + pagelink + '>\n\n' + editcount.join(' ');
 							if ( blocks.length ) blocks.forEach( block => {
-								block.text = block.text.replaceSave( '%3$s', block.by.escapeFormatting() );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toPlaintext() );
+								block.text = block.text.replaceSave( /\$3/g, block.by.escapeFormatting() );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toPlaintext() );
 								text += '\n\n**' + block.header + '**\n' + block.text;
 							} );
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) text += '\n\n<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**';
@@ -243,8 +243,8 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 					var blockedby = queryuser.blockedby;
 					var blockreason = queryuser.blockreason;
 					var block = {
-						header: lang.get('user.block.header').replaceSave( '%s', username ).escapeFormatting(),
-						text: lang.get('user.block.' + ( blockreason ? 'text' : 'noreason' )).replaceSave( '%1$s', blockedtimestamp ).replaceSave( '%2$s', blockexpiry ),
+						header: lang.get('user.block.header', username).escapeFormatting(),
+						text: lang.get('user.block.' + ( blockreason ? 'text' : 'noreason' ), blockedtimestamp, blockexpiry),
 						by: blockedby,
 						reason: blockreason
 					};
@@ -300,16 +300,16 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 					} ).finally( () => {
 						if ( msg.showEmbed() ) {
 							if ( isBlocked ) {
-								block.text = block.text.replaceSave( '%3$s', '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toMarkdown(wiki, body.query.general) );
+								block.text = block.text.replaceSave( /\$3/g, '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toMarkdown(wiki, body.query.general) );
 								embed.addField( block.header, block.text );
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) embed.addField( '\u200b', '<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**' );
 						}
 						else {
 							if ( isBlocked ) {
-								block.text = block.text.replaceSave( '%3$s', block.by.escapeFormatting() );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toPlaintext() );
+								block.text = block.text.replaceSave( /\$3/g, block.by.escapeFormatting() );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toPlaintext() );
 								text += '\n\n**' + block.header + '**\n' + block.text;
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) text += '\n\n<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**';
@@ -362,16 +362,16 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 					} ).finally( () => {
 						if ( msg.showEmbed() ) {
 							if ( isBlocked ) {
-								block.text = block.text.replaceSave( '%3$s', '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toMarkdown(wiki, body.query.general) );
+								block.text = block.text.replaceSave( /\$3/g, '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toMarkdown(wiki, body.query.general) );
 								embed.addField( block.header, block.text );
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) embed.addField( '\u200b', '<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**' );
 						}
 						else {
 							if ( isBlocked ) {
-								block.text = block.text.replaceSave( '%3$s', block.by.escapeFormatting() );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toPlaintext() );
+								block.text = block.text.replaceSave( /\$3/g, block.by.escapeFormatting() );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toPlaintext() );
 								text += '\n\n**' + block.header + '**\n' + block.text;
 							}
 							if ( msg.channel.type === 'text' && msg.guild.id in patreons ) text += '\n\n<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**';
@@ -384,13 +384,13 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 					else {
 						if ( isBlocked ) {
 							if ( msg.showEmbed() ) {
-								block.text = block.text.replaceSave( '%3$s', '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toMarkdown(wiki, body.query.general) );
+								block.text = block.text.replaceSave( /\$3/g, '[' + block.by.escapeFormatting() + '](' + wiki.toLink('User:' + block.by, '', '', body.query.general, true) + ')' );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toMarkdown(wiki, body.query.general) );
 								embed.addField( block.header, block.text );
 							}
 							else {
-								block.text = block.text.replaceSave( '%3$s', block.by.escapeFormatting() );
-								if ( block.reason ) block.text = block.text.replaceSave( '%4$s', block.reason.toPlaintext() );
+								block.text = block.text.replaceSave( /\$3/g, block.by.escapeFormatting() );
+								if ( block.reason ) block.text = block.text.replaceSave( /\$4/g, block.reason.toPlaintext() );
 								text += '\n\n**' + block.header + '**\n' + block.text;
 							}
 						}

+ 1 - 2
functions/discussion.js

@@ -278,8 +278,7 @@ function discussion_send(lang, msg, wiki, discussion, embed, spoiler) {
 			embed.setImage( discussion._embedded.contentImages[0].url );
 			break;
 		case 'POLL':
-			let count = lang.get('discussion.votes');
-			discussion.poll.answers.forEach( answer => embed.addField( answer.text.escapeFormatting(), ( answer.image ? '[__' + lang.get('discussion.image').escapeFormatting() + '__](' + answer.image.url + ')\n' : '' ) + ( count[answer.votes] || count['*' + answer.votes % 100] || count['*' + answer.votes % 10] || lang.get('discussion.votes.default') ).replace( '%s', answer.votes ), true ) );
+			discussion.poll.answers.forEach( answer => embed.addField( answer.text.escapeFormatting(), ( answer.image ? '[__' + lang.get('discussion.image').escapeFormatting() + '__](' + answer.image.url + ')\n' : '' ) + lang.get('discussion.votes', answer.votes), true ) );
 			break;
 		case 'QUIZ':
 			description = discussion.quiz.title.escapeFormatting();

+ 8 - 8
functions/global_block.js

@@ -23,8 +23,8 @@ function global_block(lang, msg, username, text, embed, wiki, spoiler) {
 				else text += '\n\n**' + lang.get('user.gblock.disabled') + '**';
 			}
 			else if ( $('.mw-warning-with-logexcerpt').length && !$(".mw-warning-with-logexcerpt .mw-logline-block").length ) {
-				if ( msg.showEmbed() ) embed.addField( lang.get('user.gblock.header').replaceSave( '%s', username ).escapeFormatting(), '\u200b' );
-				else text += '\n\n**' + lang.get('user.gblock.header').replaceSave( '%s', username ).escapeFormatting() + '**';
+				if ( msg.showEmbed() ) embed.addField( lang.get('user.gblock.header', username).escapeFormatting(), '\u200b' );
+				else text += '\n\n**' + lang.get('user.gblock.header', username).escapeFormatting() + '**';
 			}
 		}
 	}, error => {
@@ -71,20 +71,20 @@ function global_block(lang, msg, username, text, embed, wiki, spoiler) {
 				if ( expiry.startsWith( '(infiniteblock)' ) ) expiry = lang.get('user.block.until_infinity');
 				else expiry = new Date(expiry.replace( /(\d{2}:\d{2}), (\d{1,2}) \((\w+)\) (\d{4})/, '$3 $2, $4 $1 UTC' )).toLocaleString(lang.get('dateformat'), timeoptions);
 				if ( msg.showEmbed() ) {
-					var gblocktitle = lang.get('user.gblock.header').replaceSave( '%s', username ).escapeFormatting();
-					var globalblock = embed.fields.find( field => field.inline === false && field.name === lang.get('user.block.header').replaceSave( '%s', username ).escapeFormatting() && field.value.replace( /\[([^\]]*)\]\([^\)]*\)/g, '$1' ) === lang.get('user.block.' + ( reason.length > 4 ? 'text' : 'noreason' )).replaceSave( '%1$s', timestamp ).replaceSave( '%2$s', expiry ).replaceSave( '%3$s', reason[1].escapeFormatting() ).replaceSave( '%4$s', reason.slice(4).join(', ').escapeFormatting() ) );
+					var gblocktitle = lang.get('user.gblock.header', username).escapeFormatting();
+					var globalblock = embed.fields.find( field => field.inline === false && field.name === lang.get('user.block.header', username).escapeFormatting() && field.value.replace( /\[([^\]]*)\]\([^\)]*\)/g, '$1' ) === lang.get('user.block.' + ( reason.length > 4 ? 'text' : 'noreason' ), timestamp, expiry, reason[1].escapeFormatting(), reason.slice(4).join(', ').escapeFormatting()) );
 					if ( globalblock ) globalblock.name = gblocktitle;
 					else {
 						var block_wiki = reason[3].replace( /Special:BlockList$/, '' );
-						var gblocktext = lang.get('user.gblock.' + ( reason.length > 4 ? 'text' : 'noreason' )).replaceSave( '%1$s', timestamp ).replaceSave( '%2$s', expiry ).replaceSave( '%3$s', '[' + reason[1] + '](' + block_wiki + 'User:' + reason[1].toTitle(true) + ')' ).replaceSave( '%4$s', '[' + reason[2] + '](' + block_wiki + 'Special:Contribs/' + username.toTitle(true) + ')' ).replaceSave( '%5$s', reason.slice(4).join(', ').escapeFormatting() );
+						var gblocktext = lang.get('user.gblock.' + ( reason.length > 4 ? 'text' : 'noreason' ), timestamp, expiry, '[' + reason[1] + '](' + block_wiki + 'User:' + reason[1].toTitle(true) + ')', '[' + reason[2] + '](' + block_wiki + 'Special:Contribs/' + username.toTitle(true) + ')', reason.slice(4).join(', ').escapeFormatting());
 						embed.addField( gblocktitle, gblocktext );
 					}
 				}
 				else {
 					let splittext = text.split('\n\n');
-					var globalblock = splittext.indexOf('**' + lang.get('user.block.header').replaceSave( '%s', username ).escapeFormatting() + '**\n' + lang.get('user.block.' + ( reason.length > 4 ? 'text' : 'noreason' )).replaceSave( '%1$s', timestamp ).replaceSave( '%2$s', expiry ).replaceSave( '%3$s', reason[1].escapeFormatting() ).replaceSave( '%4$s', reason.slice(4).join(', ').escapeFormatting() ));
-					if ( globalblock !== -1 ) splittext[globalblock] = '**' + lang.get('user.gblock.header').replaceSave( '%s', username ).escapeFormatting() + '**\n' + lang.get('user.block.' + ( reason.length > 4 ? 'text' : 'noreason' )).replaceSave( '%1$s', timestamp ).replaceSave( '%2$s', expiry ).replaceSave( '%3$s', reason[1].escapeFormatting() ).replaceSave( '%4$s', reason.slice(4).join(', ').escapeFormatting() );
-					else splittext.push('**' + lang.get('user.gblock.header').replaceSave( '%s', username ).escapeFormatting() + '**\n' + lang.get('user.gblock.' + ( reason.length > 4 ? 'text' : 'noreason' )).replaceSave( '%1$s', timestamp ).replaceSave( '%2$s', expiry ).replaceSave( '%3$s', reason[1].escapeFormatting() ).replaceSave( '%4$s', reason[2] ).replaceSave( '%5$s', reason.slice(4).join(', ').escapeFormatting() ));
+					var globalblock = splittext.indexOf('**' + lang.get('user.block.header', username).escapeFormatting() + '**\n' + lang.get('user.block.' + ( reason.length > 4 ? 'text' : 'noreason' ), timestamp, expiry, reason[1].escapeFormatting(), reason.slice(4).join(', ').escapeFormatting()));
+					if ( globalblock !== -1 ) splittext[globalblock] = '**' + lang.get('user.gblock.header', username).escapeFormatting() + '**\n' + lang.get('user.block.' + ( reason.length > 4 ? 'text' : 'noreason' ), timestamp, expiry, reason[1].escapeFormatting(), reason.slice(4).join(', ').escapeFormatting());
+					else splittext.push('**' + lang.get('user.gblock.header', username).escapeFormatting() + '**\n' + lang.get('user.gblock.' + ( reason.length > 4 ? 'text' : 'noreason' ), timestamp, expiry, reason[1].escapeFormatting(), reason[2], reason.slice(4).join(', ').escapeFormatting()));
 					text = splittext.join('\n\n');
 				}
 			}

+ 1 - 1
functions/helpsetup.js

@@ -1,6 +1,6 @@
 function help_setup(lang, msg) {
 	msg.defaultSettings = false;
-	msg.replyMsg( lang.get('settings.missing').replaceSave( '%1$s', '`' + process.env.prefix + 'settings lang`' ).replaceSave( '%2$s', '`' + process.env.prefix + 'settings wiki`' ) );
+	msg.replyMsg( lang.get('settings.missing', '`' + process.env.prefix + 'settings lang`', '`' + process.env.prefix + 'settings wiki`') );
 }
 
 module.exports = help_setup;

+ 57 - 70
i18n/de.json

@@ -2,7 +2,9 @@
 	"__translator": [
 		"MarkusRost"
 	],
-	"lang": "de",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "de-DE",
 	"aliases": {
 		"hilfe": "help",
@@ -14,15 +16,15 @@
 		"discussions": "discussion",
 		"diskussion": "discussion"
 	},
-	"prefix": "das Präfix für diesen Server ist `%s`. Du kannst das Präfix mit `%ssettings prefix` ändern. Für eine Liste aller Befehle nutze `%shilfe`.",
+	"prefix": "das Präfix für diesen Server ist `$1`. Du kannst das Präfix mit `$1settings prefix` ändern. Für eine Liste aller Befehle nutze `$1hilfe`.",
 	"missingperm": "mir fehlen einige Berechtigungen für diesen Befehl:",
-	"limit": "🚨 **Stop, du hast ein Limit erreicht!** 🚨\n\n%s, deine Nachricht enthält zu viele Befehle!",
-	"disclaimer": "Ich bin ein kleiner Bot mit der Aufgabe auf Gamepedia- und Fandom-Wikis zu verlinken. Geschrieben wurde ich von %s in JavaScript.\n\n**Ich stehe in keinem Zusammenhang mit Fandom und bin ein inoffizieller Bot!**\n\nDu kannst mich auf Patreon unterstützen:",
+	"limit": "🚨 **Stop, du hast ein Limit erreicht!** 🚨\n\n$1, deine Nachricht enthält zu viele Befehle!",
+	"disclaimer": "Ich bin ein kleiner Bot mit der Aufgabe auf Gamepedia- und Fandom-Wikis zu verlinken. Geschrieben wurde ich von $1 in JavaScript.\n\n**Ich stehe in keinem Zusammenhang mit Fandom und bin ein inoffizieller Bot!**\n\nDu kannst mich auf Patreon unterstützen:",
 	"helpserver": "Bei Fragen oder Problemen besuche bitte meinen Support-Server:",
 	"patreon": "dies ist eine Patreon-Funktion!\nDu kannst mich auf Patreon unterstützen um Zugang zu dieser Funktion zu erhalten:",
 	"settings": {
 		"save_failed": "die Einstellungen konnten leider nicht gespeichert werden, bitte versuche es später erneut.",
-		"missing": "für diesen Server wurden noch keine Einstellungen vorgenommen. Nutze %1$s und %2$s um die Einstellungen vorzunehmen.",
+		"missing": "für diesen Server wurden noch keine Einstellungen vorgenommen. Nutze $1 und $2 um die Einstellungen vorzunehmen.",
 		"foundwikis": "Meinst du eines dieser Wikis?",
 		"current": "dies sind die aktuellen Einstellungen für diesen Server:",
 		"channel current": "dies sind die aktuellen Einstellungen für diesen Kanal:",
@@ -37,31 +39,31 @@
 		"langinvalid": "die angegebende Sprache wird nicht unterstützt!",
 		"langchanged": "du hast die Sprache für diesen Server geändert zu:",
 		"channel langchanged": "du hast die Sprache für diesen Kanal geändert zu:",
-		"langhelp": "Nutze `%s <Sprache>` um die Sprache zu ändern.\nBisher mögliche Sprachen sind:",
+		"langhelp": "Nutze `$1 <Sprache>` um die Sprache zu ändern.\nBisher mögliche Sprachen sind:",
 		"wikimissing": "für diesen Server wurde noch kein Wiki festgelegt!",
 		"wiki": "das Standard-Wiki für diesen Server ist:",
 		"channel wiki": "das Standard-Wiki für diesen Kanal ist:",
 		"wikiinvalid": "bitte gib einen gültigen Link zu einem Gamepedia- oder Fandom-Wiki an!",
 		"wikichanged": "du hast das Standard-Wiki für diesen Server geändert zu:",
 		"channel wikichanged": "du hast das Standard-Wiki für diesen Kanal geändert zu:",
-		"wikihelp": "Nutze `%s <Link>` um das Standard-Wiki zu ändern.\nLink zum Wiki: `https://<Wiki>.gamepedia.com/` oder `https://<Wiki>.fandom.com/`",
+		"wikihelp": "Nutze `$1 <Link>` um das Standard-Wiki zu ändern.\nLink zum Wiki: `https://<Wiki>.gamepedia.com/` oder `https://<Wiki>.fandom.com/`",
 		"prefix": "das Präfix für diesen Server ist:",
 		"prefixinvalid": "das angegebende Präfix wird nicht unterstützt!",
 		"prefixchanged": "du hast das Präfix für diesen Server geändert zu:",
-		"prefixhelp": "Nutze `%s <Präfix>` um das Präfix zu ändern.\nNutze `_` am Ende um ein Leerzeichen am Ende des Präfixes anzugeben.\nDas Präfix darf keine Erwähnungen enthalten!",
+		"prefixhelp": "Nutze `$1 <Präfix>` um das Präfix zu ändern.\nNutze `_` am Ende um ein Leerzeichen am Ende des Präfixes anzugeben.\nDas Präfix darf keine Erwähnungen enthalten!",
 		"inline enabled": {
 			"inline": "Inline-Befehle sind für diesen Server aktiviert.",
 			"channel inline": "Inline-Befehle sind für diesen Kanal aktiviert.",
 			"inlinechanged": "du hast Inline-Befehle für diesen Server aktiviert.",
 			"channel inlinechanged": "du hast Inline-Befehle für diesen Kanal aktiviert.",
-			"help": "Nutze `%1$s` um Inline-Befehle wie `[[%2$s]]` und `{{%2$s}}` zu deaktivieren."
+			"help": "Nutze `$1` um Inline-Befehle wie `[[$2]]` und `{{$2}}` zu deaktivieren."
 		},
 		"inline disabled": {
 			"inline": "Inline-Befehle sind für diesen Server deaktiviert.",
 			"channel inline": "Inline-Befehle sind für diesen Kanal deaktiviert.",
 			"inlinechanged": "du hast Inline-Befehle für diesen Server deaktiviert.",
 			"channel inlinechanged": "du hast Inline-Befehle für diesen Kanal deaktiviert.",
-			"help": "Nutze `%1$s` um Inline-Befehle wie `[[%2$s]]` und `{{%2$s}}` zu aktivieren."
+			"help": "Nutze `$1` um Inline-Befehle wie `[[$2]]` und `{{$2}}` zu aktivieren."
 		}
 	},
 	"pause": {
@@ -70,14 +72,14 @@
 	},
 	"voice": {
 		"text": "ich versuche allen in einem Sprachkanal eine bestimmte Rolle zu geben:",
-		"enable": "Nutze `%s` um diese Funktion zu aktivieren.",
-		"disable": "Nutze `%s` um diese Funktion zu deaktivieren.",
+		"enable": "Nutze `$1` um diese Funktion zu aktivieren.",
+		"disable": "Nutze `$1` um diese Funktion zu deaktivieren.",
 		"enabled": "du hast die Funktion, Rollen für Sprachkanäle hinzuzufügen, aktiviert.",
 		"disabled": "du hast die Funktion, Rollen für Sprachkanäle hinzuzufügen, deaktiviert.",
 		"name": "Name des Sprachkanals",
 		"channel": "Sprachkanal",
-		"join": "%1$s hat den Sprachkanal „%2$s“ betreten.",
-		"left": "%1$s hat den Sprachkanal „%2$s“ verlassen."
+		"join": "$1 hat den Sprachkanal „$2“ betreten.",
+		"left": "$1 hat den Sprachkanal „$2“ verlassen."
 	},
 	"verification": {
 		"save_failed": "die Verifizierungen konnten leider nicht gespeichert werden, bitte versuche es später erneut.",
@@ -87,7 +89,7 @@
 		"max_entries": "du hast bereits die maximale Anzahl an Verifizierungen erreicht.",
 		"no_role": "bitte gib eine Rolle für die neue Verifizierung an.",
 		"current": "dies sind die aktuellen Verifizierungen für diesen Server:",
-		"current_selected": "dies ist die Verifizierung `%s` für diesen Server:",
+		"current_selected": "dies ist die Verifizierung `$1` für diesen Server:",
 		"missing": "es sind noch keine Verifizierungen für diesen Server vorhanden.",
 		"add_more": "Füge mehr Verifizierungen hinzu:",
 		"delete_current": "Lösche diese Verifizierung:",
@@ -103,8 +105,8 @@
 		"enabled": "aktiviert",
 		"disabled": "deaktiviert",
 		"toggle": "(umschalten)",
-		"rename_no_permission": "**%s fehlt die `Nicknames verwalten` Berechtigung um Wiki-Benutzernamen zu erzwingen!**",
-		"role_too_high": "**Die Rolle %1$s ist zu hoch für %2$s um sie zu vergeben!**",
+		"rename_no_permission": "**$1 fehlt die `Nicknames verwalten` Berechtigung um Wiki-Benutzernamen zu erzwingen!**",
+		"role_too_high": "**Die Rolle $1 ist zu hoch für $2 um sie zu vergeben!**",
 		"channel_max": "du hast zu viele Kanäle angegeben.",
 		"channel_missing": "der angegebe Kanal existiert nicht.",
 		"role_max": "du hast zu viele Rollen angegeben.",
@@ -127,21 +129,21 @@
 		"failed_gblock": "**Test auf globale Sperre schlug fehl!**",
 		"failed_roles": "**Hinzufügen von Rollen schlug fehl!**",
 		"failed_rename": "**Ändern des Nickname schlug fehl!**",
-		"audit_reason": "Verifziert als „%s“",
-		"user_missing": "Der Wiki-Benutzer „%s“ existiert nicht.",
-		"user_missing_reply": "der von dir verlinkte Wiki-Benutzer „%s“ existiert nicht.",
-		"user_blocked": "**Der Wiki-Benutzer %s ist gesperrt!**",
-		"user_blocked_reply": "der von dir verlinkte Wiki-Benutzer **„%s“ ist gesperrt!**",
-		"user_gblocked": "**Der Wiki-Benutzer %s ist global gesperrt!**",
-		"user_gblocked_reply": "der von dir verlinkte Wiki-Benutzer **„%s“ ist global gesperrt!**",
-		"user_disabled": "**Der Wiki-Benutzer %s ist deaktiviert!**",
-		"user_disabled_reply": "der von dir verlinkte Wiki-Benutzer **„%s“ ist deaktiviert!**",
-		"user_failed": "Discord-Benutzer %1$s stimmt nicht mit dem Wiki-Benutzer %2$s überein.",
-		"user_failed_reply": "dein Discord-Tag stimmt nicht mit dem Wiki-Benutzer „%s“ überein.",
-		"user_matches": "Discord-Benutzer %1$s stimmt mit dem Wiki-Benutzer %2$s überein, erfüllt aber keine Kriterien für Rollen.",
-		"user_matches_reply": "dein Discord-Tag stimmt mit dem Wiki-Benutzer „%s“ überein, du erfüllst aber keine Kriterien für Rollen.",
-		"user_verified": "Discord-Benutzer %1$s wurde erfolgreich als Wiki-Benutzer %2$s verifiziert.",
-		"user_verified_reply": "du wurdest erfolgreich als Wiki-Benutzer „%s“ verifiziert.",
+		"audit_reason": "Verifziert als „$1“",
+		"user_missing": "Der Wiki-Benutzer „$1“ existiert nicht.",
+		"user_missing_reply": "der von dir verlinkte Wiki-Benutzer „$1“ existiert nicht.",
+		"user_blocked": "**Der Wiki-Benutzer $1 ist gesperrt!**",
+		"user_blocked_reply": "der von dir verlinkte Wiki-Benutzer **„$1“ ist gesperrt!**",
+		"user_gblocked": "**Der Wiki-Benutzer $1 ist global gesperrt!**",
+		"user_gblocked_reply": "der von dir verlinkte Wiki-Benutzer **„$1“ ist global gesperrt!**",
+		"user_disabled": "**Der Wiki-Benutzer $1 ist deaktiviert!**",
+		"user_disabled_reply": "der von dir verlinkte Wiki-Benutzer **„$1“ ist deaktiviert!**",
+		"user_failed": "Discord-Benutzer $1 stimmt nicht mit dem Wiki-Benutzer $2 überein.",
+		"user_failed_reply": "dein Discord-Tag stimmt nicht mit dem Wiki-Benutzer „$1“ überein.",
+		"user_matches": "Discord-Benutzer $1 stimmt mit dem Wiki-Benutzer $2 überein, erfüllt aber keine Kriterien für Rollen.",
+		"user_matches_reply": "dein Discord-Tag stimmt mit dem Wiki-Benutzer „$1“ überein, du erfüllst aber keine Kriterien für Rollen.",
+		"user_verified": "Discord-Benutzer $1 wurde erfolgreich als Wiki-Benutzer $2 verifiziert.",
+		"user_verified_reply": "du wurdest erfolgreich als Wiki-Benutzer „$1“ verifiziert.",
 		"user_renamed": "Sein Discord-Nickname wurde zu seinem Wiki-Benutzernamen geändert.",
 		"discord": "Discord-Benutzer:",
 		"wiki": "Wiki-Benutzer:",
@@ -149,10 +151,10 @@
 		"notice": "Hinweis:",
 		"qualified": "Qualifiziert für:",
 		"qualified_error": "Qualifiziert für, aber konnte nicht hinzugefügt werden:",
-		"help_guide": "Folge [dieser Anleitung](%s) um dein Discord-Tag auf deinem Wiki-Profil hinzuzufügen:",
+		"help_guide": "Folge [dieser Anleitung]($1) um dein Discord-Tag auf deinem Wiki-Profil hinzuzufügen:",
 		"help_gamepedia": "https://help.gamepedia.com/Gamepedia_Help_Wiki:Discord_verification",
 		"help_fandom": "https://community.fandom.com/wiki/Special:VerifyUser",
-		"help_subpage": "Bitte füge dein Discord-Tag (%s) zu deiner Discord-Unterseite im Wiki hinzu:"
+		"help_subpage": "Bitte füge dein Discord-Tag ($1) zu deiner Discord-Unterseite im Wiki hinzu:"
 	},
 	"overview": {
 		"inaccurate": "Statistiken können ungenau sein",
@@ -243,17 +245,17 @@
 			"loading": "Lade globale Statistiken…"
 		},
 		"block": {
-			"header": "%s ist derzeit gesperrt!",
-			"text": "Gesperrt am %1$s bis zum %2$s von %3$s mit der Begründung „%4$s“.",
-			"noreason": "Gesperrt am %1$s bis zum %2$s von %3$s.",
-			"nofromtext": "Gesperrt bis zum %2$s von %3$s mit der Begründung „%4$s“.",
-			"nofromnoreason": "Gesperrt bis zum %2$s von %3$s.",
+			"header": "$1 ist derzeit gesperrt!",
+			"text": "Gesperrt am $1 bis zum $2 von $3 mit der Begründung „$4“.",
+			"noreason": "Gesperrt am $1 bis zum $2 von $3.",
+			"nofromtext": "Gesperrt bis zum $2 von $3 mit der Begründung „$4“.",
+			"nofromnoreason": "Gesperrt bis zum $2 von $3.",
 			"until_infinity": "Ende aller Tage"
 		},
 		"gblock": {
-			"header": "%s ist derzeit global gesperrt!",
-			"text": "Gesperrt am %1$s bis zum %2$s von %3$s auf %4$s mit der Begründung „%5$s“.",
-			"noreason": "Gesperrt am %1$s bis zum %2$s von %3$s auf %4$s.",
+			"header": "$1 ist derzeit global gesperrt!",
+			"text": "Gesperrt am $1 bis zum $2 von $3 auf $4 mit der Begründung „$5“.",
+			"noreason": "Gesperrt am $1 bis zum $2 von $3 auf $4.",
 			"disabled": "Dieses Konto ist derzeit deaktiviert!"
 		}
 	},
@@ -265,7 +267,7 @@
 			"editor": "Autor:",
 			"timestamp": "Zeitpunkt:",
 			"size": "Unterschied:",
-			"bytes": "%s Bytes",
+			"bytes": "$1 {{PLURAL:$1|Byte|Bytes}}",
 			"comment": "Zusammenfassung:",
 			"tags": "Markierungen:",
 			"removed": "Entfernt:",
@@ -277,39 +279,24 @@
 	"search": {
 		"page": "seite",
 		"search": "suche",
-		"infopage": "Nicht das richtige Ergebnis? Nutze %s für einen direkten Link.",
-		"infosearch": "Nicht das richtige Ergebnis? Nutze %1$s für einen direkten Link oder %2$s für eine Liste mit allen Treffern.",
+		"infopage": "Nicht das richtige Ergebnis? Nutze $1 für einen direkten Link.",
+		"infosearch": "Nicht das richtige Ergebnis? Nutze $1 für einen direkten Link oder $2 für eine Liste mit allen Treffern.",
 		"category": {
 			"content": "Inhalt der Kategorie:",
 			"empty": "*Diese Kategorie ist leer*",
-			"pages": {
-				"default": "%s Seiten",
-				"1": "Eine Seite"
-			},
-			"files": {
-				"default": "%s Dateien",
-				"1": "Eine Datei"
-			},
-			"subcats": {
-				"default": "%s Kategorien",
-				"1": "Eine Kategorie"
-			}
+			"pages":  "{{PLURAL:$1|Eine Seite|$1 Seiten}}",
+			"files": "{{PLURAL:$1|Eine Datei|$1 Dateien}}",
+			"subcats": "{{PLURAL:$1|Eine Kategorie|$1 Kategorien}}"
 		},
 		"special": "Inhalt der Spezialseite:",
 		"empty": "*Diese Spezialseite ist leer*",
-		"results": {
-			"default": "%s Ergebnisse insgesamt",
-			"1": "%s Ergebnis insgesamt"
-		}
+		"results": "$1 {{PLURAL:$1|Ergebnis|Ergebnisse}} insgesamt"
 	},
 	"discussion": {
 		"post": "post",
 		"main": "Diskussionen",
 		"image": "Bild öffnen",
-		"votes": {
-			"default": "%s Stimmen",
-			"1": "Eine Stimme"
-		}
+		"votes": "{{PLURAL:$1|Eine Stimme|$1 Stimmen}}"
 	},
 	"invite": {
 		"bot": "Du kannst mich mit diesem Link auf einen anderen Server einladen:"
@@ -327,9 +314,9 @@
 		"default": "ich bin voll funktionsfähig!",
 		"time": "Ping",
 		"notice": "Eingeschränkte Funktionalität",
-		"MediaWiki": "Benötigt mindestens %1$s für vollständige Funktionalität, fand `%2$s`.",
-		"TextExtracts": "Benötigt die Erweiterung %s für Seiten-Beschreibungen.",
-		"PageImages": "Benötigt die Erweiterung %s für Seiten-Vorschaubilder."
+		"MediaWiki": "Benötigt mindestens $1 für vollständige Funktionalität, fand `$2`.",
+		"TextExtracts": "Benötigt die Erweiterung $1 für Seiten-Beschreibungen.",
+		"PageImages": "Benötigt die Erweiterung $1 für Seiten-Vorschaubilder."
 	},
 	"help": {
 		"all": "Du willst also wissen, was ich so drauf habe? Hier ist eine Liste aller Befehle, die ich verstehe:",
@@ -408,7 +395,7 @@
 		},
 		"private": "**Privater Fehler**",
 		"fixed": "Lösungsversion:",
-		"more": "Und %s mehr.",
-		"total": "%s Fehler korrigiert"
+		"more": "Und $1 {{PLURAL:$1|mehr}}.",
+		"total": "$1 {{PLURAL:$1|Fehler}} korrigiert"
 	}
 }

+ 56 - 70
i18n/en.json

@@ -3,21 +3,22 @@
 		"MarkusRost",
 		"violine1101"
 	],
-	"lang": "en",
+	"fallback": [
+	],
 	"dateformat": "en-US",
 	"aliases": {
 		"🎲": "random",
 		"discussions": "discussion"
 	},
-	"prefix": "the prefix for this server is `%s`. You can change the prefix with `%ssettings prefix`. For a list of all commands see `%shelp`.",
+	"prefix": "the prefix for this server is `$1`. You can change the prefix with `$1settings prefix`. For a list of all commands see `$1help`.",
 	"missingperm": "I'm missing some permissions for this command:",
-	"limit": "🚨 **Stop, you hit a limit!** 🚨\n\n%s, your message contained too many commands!",
-	"disclaimer": "I am a small bot with the task to link to Gamepedia and Fandom wikis. %s wrote me in JavaScript.\n\n**I am not affiliated with Fandom and am an unofficial tool!**\n\nYou can support me on Patreon:",
+	"limit": "🚨 **Stop, you hit a limit!** 🚨\n\n$1, your message contained too many commands!",
+	"disclaimer": "I am a small bot with the task to link to Gamepedia and Fandom wikis. $1 wrote me in JavaScript.\n\n**I am not affiliated with Fandom and am an unofficial tool!**\n\nYou can support me on Patreon:",
 	"helpserver": "For questions and problems please visit my support server:",
 	"patreon": "this is a Patreon only feature!\nYou can support me on Patreon to get access to this feature:",
 	"settings": {
 		"save_failed": "sadly the settings couldn't be saved, please try again later.",
-		"missing": "this server isn't set up yet. Use %1$s and %2$s to change the settings.",
+		"missing": "this server isn't set up yet. Use $1 and $2 to change the settings.",
 		"foundwikis": "Do you mean any of these wikis?",
 		"current": "these are the current settings for this server:",
 		"channel current": "these are the current settings for this channel:",
@@ -32,31 +33,31 @@
 		"langinvalid": "the specified language is not supported!",
 		"langchanged": "you changed the language for this server to:",
 		"channel langchanged": "you changed the language for this channel to:",
-		"langhelp": "Use `%s <language>` to change the language.\nCurrently supported languages are:",
+		"langhelp": "Use `$1 <language>` to change the language.\nCurrently supported languages are:",
 		"wikimissing": "no default wiki is set for this server yet!",
 		"wiki": "the default wiki for this server is:",
 		"channel wiki": "the default wiki for this channel is:",
 		"wikiinvalid": "please provide a valid link to a Gamepedia or Fandom wiki!",
 		"wikichanged": "you changed the default wiki for this server to:",
 		"channel wikichanged": "you changed the default wiki for this channel to:",
-		"wikihelp": "Use `%s <link>` to change the default wiki.\nLink to the wiki: `https://<wiki>.gamepedia.com/` or `https://<wiki>.fandom.com/`",
+		"wikihelp": "Use `$1 <link>` to change the default wiki.\nLink to the wiki: `https://<wiki>.gamepedia.com/` or `https://<wiki>.fandom.com/`",
 		"prefix": "the prefix for this server is:",
 		"prefixinvalid": "the specified prefix is not supported!",
 		"prefixchanged": "you changed the prefix for this server to:",
-		"prefixhelp": "Use `%s <prefix>` to change the prefix.\nUse `_` at the end to indicate a space at end of the prefix.\nThe prefix may not include mentions!",
+		"prefixhelp": "Use `$1 <prefix>` to change the prefix.\nUse `_` at the end to indicate a space at end of the prefix.\nThe prefix may not include mentions!",
 		"inline enabled": {
 			"inline": "inline commands are currently enabled for this server.",
 			"channel inline": "inline commands are currently enabled for this channel.",
 			"inlinechanged": "you enabled inline commands for this server.",
 			"channel inlinechanged": "you enabled inline commands for this channel.",
-			"help": "Use `%1$s` to disable inline commands like `[[%2$s]]` and `{{%2$s}}`."
+			"help": "Use `$1` to disable inline commands like `[[$2]]` and `{{$2}}`."
 		},
 		"inline disabled": {
 			"inline": "inline commands are currently disabled for this server.",
 			"channel inline": "inline commands are currently disabled for this channel.",
 			"inlinechanged": "you disabled inline commands for this server.",
 			"channel inlinechanged": "you disabled inline commands for this channel.",
-			"help": "Use `%1$s` to enable inline commands like `[[%2$s]]` and `{{%2$s}}`."
+			"help": "Use `$1` to enable inline commands like `[[$2]]` and `{{$2}}`."
 		}
 	},
 	"pause": {
@@ -65,14 +66,14 @@
 	},
 	"voice": {
 		"text": "I try to give everyone in a voice channel a specific role:",
-		"enable": "Use `%s` to enable this function.",
-		"disable": "Use `%s` to disable this function.",
+		"enable": "Use `$1` to enable this function.",
+		"disable": "Use `$1` to disable this function.",
 		"enabled": "you enabled the function to add roles for voice channels.",
 		"disabled": "you disabled the function to add roles for voice channels.",
 		"name": "voice channel name",
 		"channel": "Voice channel",
-		"join": "%1$s joined the voice channel \"%2$s\".",
-		"left": "%1$s left the voice channel \"%2$s\"."
+		"join": "$1 joined the voice channel \"$2\".",
+		"left": "$1 left the voice channel \"$2\"."
 	},
 	"verification": {
 		"save_failed": "sadly the verification couldn't be saved, please try again later.",
@@ -82,7 +83,7 @@
 		"max_entries": "you already reached the maximal amount of verifications.",
 		"no_role": "please provide a role for the new verification.",
 		"current": "these are the current verifications for this server:",
-		"current_selected": "this is the verification `%s` for this server:",
+		"current_selected": "this is the verification `$1` for this server:",
 		"missing": "there are no verifications for this server yet.",
 		"add_more": "Add more verifications:",
 		"delete_current": "Delete this verification:",
@@ -98,8 +99,8 @@
 		"enabled": "enabled",
 		"disabled": "disabled",
 		"toggle": "(toggle)",
-		"rename_no_permission": "**%s is missing the `Manage Nicknames` permission to force wiki usernames!**",
-		"role_too_high": "**The role %1$s is too high for %2$s to assign!**",
+		"rename_no_permission": "**$1 is missing the `Manage Nicknames` permission to force wiki usernames!**",
+		"role_too_high": "**The role $1 is too high for $2 to assign!**",
 		"channel_max": "you provided too many channels.",
 		"channel_missing": "the provided channel does not exist.",
 		"role_max": "you provided too many roles.",
@@ -122,21 +123,21 @@
 		"failed_gblock": "**Check for global block failed!**",
 		"failed_roles": "**Adding roles failed!**",
 		"failed_rename": "**Changing the nickname failed!**",
-		"audit_reason": "Verified as \"%s\"",
-		"user_missing": "The wiki user \"%s\" doesn't exist.",
-		"user_missing_reply": "your linked wiki user \"%s\" doesn't exist.",
-		"user_blocked": "**The wiki user %s is blocked!**",
-		"user_blocked_reply": "your linked wiki user **\"%s\" is blocked!**",
-		"user_gblocked": "**The wiki user %s is globally blocked!**",
-		"user_gblocked_reply": "your linked wiki user **\"%s\" is globally blocked!**",
-		"user_disabled": "**The wiki user %s is disabled!**",
-		"user_disabled_reply": "your linked wiki user **\"%s\" is disabled!**",
-		"user_failed": "Discord user %1$s doesn't match the wiki user %2$s.",
-		"user_failed_reply": "your Discord tag doesn't match the wiki user \"%s\".",
-		"user_matches": "Discord user %1$s matches the wiki user %2$s, but doesn't meet the requirements for any roles.",
-		"user_matches_reply": "your Discord tag matches the wiki user \"%s\", but you don't meet the requirements for any roles.",
-		"user_verified": "Discord user %1$s has been successfully verified as wiki user %2$s.",
-		"user_verified_reply": "you have been successfully verified as wiki user \"%s\".",
+		"audit_reason": "Verified as \"$1\"",
+		"user_missing": "The wiki user \"$1\" doesn't exist.",
+		"user_missing_reply": "your linked wiki user \"$1\" doesn't exist.",
+		"user_blocked": "**The wiki user $1 is blocked!**",
+		"user_blocked_reply": "your linked wiki user **\"$1\" is blocked!**",
+		"user_gblocked": "**The wiki user $1 is globally blocked!**",
+		"user_gblocked_reply": "your linked wiki user **\"$1\" is globally blocked!**",
+		"user_disabled": "**The wiki user $1 is disabled!**",
+		"user_disabled_reply": "your linked wiki user **\"$1\" is disabled!**",
+		"user_failed": "Discord user $1 doesn't match the wiki user $2.",
+		"user_failed_reply": "your Discord tag doesn't match the wiki user \"$1\".",
+		"user_matches": "Discord user $1 matches the wiki user $2, but doesn't meet the requirements for any roles.",
+		"user_matches_reply": "your Discord tag matches the wiki user \"$1\", but you don't meet the requirements for any roles.",
+		"user_verified": "Discord user $1 has been successfully verified as wiki user $2.",
+		"user_verified_reply": "you have been successfully verified as wiki user \"$1\".",
 		"user_renamed": "Their Discord nickname has been changed to their wiki username.",
 		"discord": "Discord user:",
 		"wiki": "Wiki user:",
@@ -144,10 +145,10 @@
 		"notice": "Notice:",
 		"qualified": "Qualified for:",
 		"qualified_error": "Qualified for, but could not add:",
-		"help_guide": "Follow [this guide](%s) to add your Discord tag to your wiki profile:",
+		"help_guide": "Follow [this guide]($1) to add your Discord tag to your wiki profile:",
 		"help_gamepedia": "https://help.gamepedia.com/Gamepedia_Help_Wiki:Discord_verification",
 		"help_fandom": "https://community.fandom.com/wiki/Special:VerifyUser",
-		"help_subpage": "Please add your Discord tag (%s) to your Discord subpage on the wiki:"
+		"help_subpage": "Please add your Discord tag ($1) to your Discord subpage on the wiki:"
 	},
 	"overview": {
 		"inaccurate": "Statistics may be inaccurate",
@@ -239,17 +240,17 @@
 			"loading": "Loading global statistics…"
 		},
 		"block": {
-			"header": "%s is currently blocked!",
-			"text": "Blocked on %1$s until %2$s by %3$s with reason \"%4$s\".",
-			"noreason": "Blocked on %1$s until %2$s by %3$s.",
-			"nofromtext": "Blocked until %2$s by %3$s with reason \"%4$s\".",
-			"nofromnoreason": "Blocked until %2$s by %3$s.",
+			"header": "$1 is currently blocked!",
+			"text": "Blocked on $1 until $2 by $3 with reason \"$4\".",
+			"noreason": "Blocked on $1 until $2 by $3.",
+			"nofromtext": "Blocked until $2 by $3 with reason \"$4\".",
+			"nofromnoreason": "Blocked until $2 by $3.",
 			"until_infinity": "the end of all days"
 		},
 		"gblock": {
-			"header": "%s is currently globally blocked!",
-			"text": "Blocked on %1$s until %2$s by %3$s on %4$s with reason \"%5$s\".",
-			"noreason": "Blocked on %1$s until %2$s by %3$s on %4$s.",
+			"header": "$1 is currently globally blocked!",
+			"text": "Blocked on $1 until $2 by $3 on $4 with reason \"$5\".",
+			"noreason": "Blocked on $1 until $2 by $3 on $4.",
 			"disabled": "This account is currently disabled!"
 		}
 	},
@@ -261,7 +262,7 @@
 			"editor": "Editor:",
 			"timestamp": "Edit date:",
 			"size": "Difference:",
-			"bytes": "%s Bytes",
+			"bytes": "$1 {{PLURAL:$1|Byte|Bytes}}",
 			"comment": "Comment:",
 			"tags": "Tags:",
 			"removed": "Removed:",
@@ -273,39 +274,24 @@
 	"search": {
 		"page": "page",
 		"search": "search",
-		"infopage": "Not the correct result? Use %s for a direct link.",
-		"infosearch": "Not the correct result? Use %1$s for a direct link or %2$s for a list of all hits.",
+		"infopage": "Not the correct result? Use $1 for a direct link.",
+		"infosearch": "Not the correct result? Use $1 for a direct link or $2 for a list of all hits.",
 		"category": {
 			"content": "Content of this category:",
 			"empty": "*This category is empty*",
-			"pages": {
-				"default": "%s pages",
-				"1": "%s page"
-			},
-			"files": {
-				"default": "%s files",
-				"1": "%s file"
-			},
-			"subcats": {
-				"default": "%s categories",
-				"1": "%s category"
-			}
+			"pages": "$1 {{PLURAL:$1|page|pages}}",
+			"files": "$1 {{PLURAL:$1|file|files}}",
+			"subcats": "$1 {{PLURAL:$1|category|categories}}"
 		},
 		"special": "Content of this special page:",
 		"empty": "*This special page is empty*",
-		"results": {
-			"default": "%s total results",
-			"1": "%s total result"
-		}
+		"results": "$1 total {{PLURAL:$1|result|results}}"
 	},
 	"discussion": {
 		"post": "post",
 		"main": "Discussions",
 		"image": "View Image",
-		"votes": {
-			"default": "%s votes",
-			"1": "%s vote"
-		}
+		"votes": "$1 {{PLURAL:$1|vote|votes}}"
 	},
 	"invite": {
 		"bot": "Use this link to invite me to another server:"
@@ -322,9 +308,9 @@
 		"default": "I'm fully functional!",
 		"time": "Response time",
 		"notice": "Limited functionality",
-		"MediaWiki": "Requires at least %1$s for full functionality, found `%2$s`.",
-		"TextExtracts": "Requires the extension %s for page descriptions.",
-		"PageImages": "Requires the extension %s for page thumbnails."
+		"MediaWiki": "Requires at least $1 for full functionality, found `$2`.",
+		"TextExtracts": "Requires the extension $1 for page descriptions.",
+		"PageImages": "Requires the extension $1 for page thumbnails."
 	},
 	"help": {
 		"all": "So, you want to know what things I can do? Here is a list of all commands that I understand:",
@@ -389,7 +375,7 @@
 		},
 		"private": "**Private Issue**",
 		"fixed": "Fixed Version:",
-		"more": "And %s more.",
-		"total": "%s issues fixed"
+		"more": "And $1 {{PLURAL:$1|more}}.",
+		"total": "$1 {{PLURAL:$1|issue|issues}} fixed"
 	}
 }

+ 30 - 41
i18n/fr.json

@@ -3,7 +3,9 @@
 		"Yanis48",
 		"JSBM"
 	],
-	"lang": "fr",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "fr-FR",
 	"aliases": {
 		"aide": "help",
@@ -11,15 +13,15 @@
 		"🎲": "random",
 		"discussions": "discussion"
 	},
-	"prefix": "le préfixe pour ce serveur est `%s`. Vous pouvez changer le préfixe avec `%ssettings prefix`. Pour une liste de toutes les commandes, voir `%shelp`.",
+	"prefix": "le préfixe pour ce serveur est `$1`. Vous pouvez changer le préfixe avec `$1settings prefix`. Pour une liste de toutes les commandes, voir `$1help`.",
 	"missingperm": "Il me manque certaines permissions pour cette commande :",
-	"limit": "🚨 **Halte ! Vous avez dépassé la limite !** 🚨\n\n%s, votre message contient beaucoup trop de commandes !",
-	"disclaimer": "Je suis un petit bot avec la tâche de créer un lien vers les wikis Gamepedia et Fandom. %s m'a écrit en JavaScript.\n\n**Je ne suis pas affilié avec Fandom et ne suis pas un outil officiel !**\n\nVous pouvez également me soutenir sur Patreon :",
+	"limit": "🚨 **Halte ! Vous avez dépassé la limite !** 🚨\n\n$1, votre message contient beaucoup trop de commandes !",
+	"disclaimer": "Je suis un petit bot avec la tâche de créer un lien vers les wikis Gamepedia et Fandom. $1 m'a écrit en JavaScript.\n\n**Je ne suis pas affilié avec Fandom et ne suis pas un outil officiel !**\n\nVous pouvez également me soutenir sur Patreon :",
 	"helpserver": "Pour tout problème ou question, merci de visiter mon serveur de support :",
 	"patreon": "c'est une fonctionnalité réservée au Patreon !\nVous pouver me supporter sur Patreon pour avoir accès à cette fonctionnalité :",
 	"settings": {
 		"save_failed": "malheureusement, les paramètres n'ont pas pu être sauvegardés, veuillez réessayer plus tard.",
-		"missing": "ce serveur n'est pas encore configuré. Utilisez %1$s et %2$s pour modifier les paramètres.",
+		"missing": "ce serveur n'est pas encore configuré. Utilisez $1 et $2 pour modifier les paramètres.",
 		"foundwikis": "Cherchiez-vous un de ces wikis ?",
 		"current": "voici les paramètres actuels pour ce serveur :",
 		"channel current": "voici les paramètres actuels pour ce salon :",
@@ -33,14 +35,14 @@
 		"langinvalid": "la langue spécifiée n'est pas disponible !",
 		"langchanged": "vous avez changé la langue pour ce serveur en :",
 		"channel langchanged": "vous avez changé la langue pour ce salon en :",
-		"langhelp": "Entrez `%s <langue>` pour changez la langue.\nLes langues disponibles sont les suivantes :",
+		"langhelp": "Entrez `$1 <langue>` pour changez la langue.\nLes langues disponibles sont les suivantes :",
 		"wikimissing": "aucun wiki n'est encore défini pour ce serveur.",
 		"wiki": "le wiki associé à ce serveur est :",
 		"channel wiki": "le wiki défini pour ce salon est :",
 		"wikiinvalid": "veuillez fournir un lien valide vers un wiki de Gamepedia ou Fandom !",
 		"wikichanged": "le wiki par défaut a été changé pour :",
 		"channel wikichanged": "le wiki défini pour ce salon sera désormais :",
-		"wikihelp": "Utilisez `%s <lien>` pour changer le wiki par défaut.\nLien du wiki : `https://<wiki>.gamepedia.com/` ou `https://<wiki>.fandom.com/`",
+		"wikihelp": "Utilisez `$1 <lien>` pour changer le wiki par défaut.\nLien du wiki : `https://<wiki>.gamepedia.com/` ou `https://<wiki>.fandom.com/`",
 		"prefix": "le préfixe pour ce serveur est :",
 		"prefixinvalid": "le préfixe spécifié n'est pas supporté !",
 		"prefixchanged": "vous avez changé le préfixe pour ce serveur en :"
@@ -51,14 +53,14 @@
 	},
 	"voice": {
 		"text": "j'essaye de donner un rôle spécifique à tout le monde dans un salon vocal :",
-		"enable": "Utilisez `%s` pour activer cette fonction.",
-		"disable": "Utilisez `%s` pour désactiver cette fonction.",
+		"enable": "Utilisez `$1` pour activer cette fonction.",
+		"disable": "Utilisez `$1` pour désactiver cette fonction.",
 		"enabled": "vous avez activé la fonction pour ajouter des rôles aux salons vocaux.",
 		"disabled": "vous avez désactivé la fonction pour ajouter des rôles aux salons vocaux.",
 		"name": "nom du salon vocal",
 		"channel": "Salon vocal",
-		"join": "%1$s a rejoint le salon vocal \"%2$s\".",
-		"left": "%1$s a quitté le salon vocal \"%2$s\"."
+		"join": "$1 a rejoint le salon vocal \"$2\".",
+		"left": "$1 a quitté le salon vocal \"$2\"."
 	},
 	"overview": {
 		"inaccurate": "Les statistiques peuvent être inexactes",
@@ -138,17 +140,17 @@
 			"loading": "Chargement des statistiques globales…"
 		},
 		"block": {
-			"header": "%s est actuellement bloqué !",
-			"text": "Bloqué le %1$s jusqu'au %2$s par %3$s avec la raison « %4$s ».",
-			"noreason": "Bloqué le %1$s jusqu'au %2$s par %3$s.",
-			"nofromtext": "Bloqué jusqu'au %2$s par %3$s avec la raison « %4$s ».",
-			"nofromnoreason": "Bloqué jusqu'au %2$s par %3$s.",
+			"header": "$1 est actuellement bloqué !",
+			"text": "Bloqué le $1 jusqu'au $2 par $3 avec la raison « $4 ».",
+			"noreason": "Bloqué le $1 jusqu'au $2 par $3.",
+			"nofromtext": "Bloqué jusqu'au $2 par $3 avec la raison « $4 ».",
+			"nofromnoreason": "Bloqué jusqu'au $2 par $3.",
 			"until_infinity": "moment de la fin des jours"
 		},
 		"gblock": {
-			"header": "%s est actuellement bloqué globalement !",
-			"text": "Bloqué le %1$s jusqu'au %2$s par %3$s sur %4$s, pour la raison « %5$s ».",
-			"noreason": "Bloqué le %1$s jusqu'au %2$s par %3$s sur %4$s.",
+			"header": "$1 est actuellement bloqué globalement !",
+			"text": "Bloqué le $1 jusqu'au $2 par $3 sur $4, pour la raison « $5 ».",
+			"noreason": "Bloqué le $1 jusqu'au $2 par $3 sur $4.",
 			"disabled": "Ce compte est actuellement désactivé !"
 		}
 	},
@@ -160,7 +162,7 @@
 			"editor": "Éditeur :",
 			"timestamp": "Date :",
 			"size": "Différence :",
-			"bytes": "%s octets",
+			"bytes": "$1 {{PLURAL:$1|octet|octets}}",
 			"comment": "Commentaire :",
 			"tags": "Balises :",
 			"removed": "Retirés :",
@@ -172,23 +174,14 @@
 	"search": {
 		"page": "page",
 		"search": "chercher",
-		"infopage": "Pas le bon résultat ? Utilisez %s pour un lien direct.",
-		"infosearch": "Pas le bon résultat ? Utilisez %1$s pour un lien direct ou %2$s pour une liste de toutes les correspondances.",
+		"infopage": "Pas le bon résultat ? Utilisez $1 pour un lien direct.",
+		"infosearch": "Pas le bon résultat ? Utilisez $1 pour un lien direct ou $2 pour une liste de toutes les correspondances.",
 		"category": {
 			"content": "Contenu de cette catégorie :",
 			"empty": "*Cette catégorie est vide*",
-			"pages": {
-				"default": "%s pages",
-				"1": "%s page"
-			},
-			"files": {
-				"default": "%s fichiers",
-				"1": "%s fichier"
-			},
-			"subcats": {
-				"default": "%s catégories",
-				"1": "%s catégorie"
-			}
+			"pages": "$1 {{PLURAL:$1|page|pages}}",
+			"files": "$1 {{PLURAL:$1|fichier|fichiers}}",
+			"subcats": "$1 {{PLURAL:$1|catégorie|catégories}}"
 		},
 		"special": "Contenu de cette page spéciale :",
 		"empty": "*Cette page spéciale est vide*"
@@ -197,11 +190,7 @@
 		"post": "post",
 		"main": "Discussions",
 		"image": "Afficher l'image",
-		"votes": {
-			"default": "%s votes",
-			"0": "%s vote",
-			"1": "%s vote"
-		}
+		"votes": "$1 {{PLURAL:$1|vote|votes}}"
 	},
 	"invite": {
 		"bot": "Utilisez ce lien pour m'inviter sur un autre serveur :"
@@ -310,7 +299,7 @@
 		},
 		"private": "**Problème privé**",
 		"fixed": "Corrigé lors de la version",
-		"more": "Et %s autres.",
-		"total": "%s bugs corrigés"
+		"more": "Et $1 {{PLURAL:$1|autres}}.",
+		"total": "$1 {{PLURAL:$1|bug|bugs}} corrigés"
 	}
 }

+ 30 - 40
i18n/nl.json

@@ -2,7 +2,9 @@
 	"__translator": [
 		"Jack_McKalling"
 	],
-	"lang": "nl",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "nl-NL",
 	"aliases": {
 		"pagina": "page",
@@ -13,15 +15,15 @@
 		"discussions": "discussion",
 		"discussie": "discussion"
 	},
-	"prefix": "het voorvoegsel voor deze server is `%s`. Je kunt het voorvoegsel veranderen met `%ssettings prefix`. Voor een lijst van alle opdrachten zie `%shelp`.",
+	"prefix": "het voorvoegsel voor deze server is `$1`. Je kunt het voorvoegsel veranderen met `$1settings prefix`. Voor een lijst van alle opdrachten zie `$1help`.",
 	"missingperm": "Ik mis een aantal permissies voor deze opdracht:",
-	"limit": "🚨 **Stop, je hebt een limiet bereikt!** 🚨\n\n%s, je bericht bevatte te veel opdrachten!",
-	"disclaimer": "Ik ben een kleine bot met de taak om naar Gamepedia en Fandom wiki's te linken. %s schreef me in JavaScript.\n\n**Ik ben niet geaffilieerd met Fandom en ben een onofficieel hulpmiddel!**\n\nJe kunt me ondersteunen op Patreon:",
+	"limit": "🚨 **Stop, je hebt een limiet bereikt!** 🚨\n\n$1, je bericht bevatte te veel opdrachten!",
+	"disclaimer": "Ik ben een kleine bot met de taak om naar Gamepedia en Fandom wiki's te linken. $1 schreef me in JavaScript.\n\n**Ik ben niet geaffilieerd met Fandom en ben een onofficieel hulpmiddel!**\n\nJe kunt me ondersteunen op Patreon:",
 	"helpserver": "Voor vragen en problemen verwijs ik je graag naar mijn support server:",
 	"patreon": "dit is een Patreon-exclusieve functie!\nJe kunt contact met me opnemen via Patreon om toegang tot deze functie te krijgen:",
 	"settings": {
 		"save_failed": "helaas konden de instellingen niet opgeslagen worden, probeer het later nog een keer.",
-		"missing": "deze server is nog niet geconfigureerd. Gebruik %1$s en %2$s om de instellingen  te wijzigen.",
+		"missing": "deze server is nog niet geconfigureerd. Gebruik $1 en $2 om de instellingen  te wijzigen.",
 		"foundwikis": "Bedoelde je één van deze wiki's?",
 		"current": "dit zijn de huidige instellingen voor deze server:",
 		"channel current": "dit zijn de huidige instellingen voor dit kanaal:",
@@ -35,14 +37,14 @@
 		"langinvalid": "de opgegeven taal wordt niet ondersteund!",
 		"langchanged": "je hebt de taal voor deze server gewijzigd naar:",
 		"channel langchanged": "je hebt de taal voor dit kanaal gewijzigd naar:",
-		"langhelp": "Gebruik `%s <taal>` om de taal te wijzigen.\nOp dit moment zijn de ondersteunde talen:",
+		"langhelp": "Gebruik `$1 <taal>` om de taal te wijzigen.\nOp dit moment zijn de ondersteunde talen:",
 		"wikimissing": "voor deze server is nog geen standaard wiki ingesteld!",
 		"wiki": "de standaard wiki voor deze server is:",
 		"channel wiki": "de standaard wiki voor dit kanaal is:",
 		"wikiinvalid": "geef a.u.b. een geldige link naar een Gamepedia of Fandom wiki!",
 		"wikichanged": "je wijzigde de standaard wiki voor deze server naar:",
 		"channel wikichanged": "je wijzigde de standaard wiki voor dit kanaal naar:",
-		"wikihelp": "Gebruik `%s <link>` om de standaard wiki te wijzigen.\nLink naar de wiki: `https://<wiki>.gamepedia.com/` of `https://<wiki>.fandom.com/`",
+		"wikihelp": "Gebruik `$1 <link>` om de standaard wiki te wijzigen.\nLink naar de wiki: `https://<wiki>.gamepedia.com/` of `https://<wiki>.fandom.com/`",
 		"prefix": "het voorvoegsel voor deze server is:",
 		"prefixinvalid": "het opgegeven voorvoegsel wordt niet ondersteund!",
 		"prefixchanged": "je hebt het voorvoegsel voor deze server veranderd naar:"
@@ -53,14 +55,14 @@
 	},
 	"voice": {
 		"text": "Ik probeer iedereen in een spraakkanaal een specifieke rol te geven:",
-		"enable": "Gebruik `%s` om deze functie in te schakelen.",
-		"disable": "Gebruik `%s` om deze functie uit te schakelen.",
+		"enable": "Gebruik `$1` om deze functie in te schakelen.",
+		"disable": "Gebruik `$1` om deze functie uit te schakelen.",
 		"enabled": "je hebt de functie ingeschakeld om rollen toe te voegen aan spraakkanalen.",
 		"disabled": "je hebt de functie uitgeschakeld om rollen toe te voegen aan spraakkanalen.",
 		"name": "spraakkanaal naam",
 		"channel": "Spraakkanaal",
-		"join": "%1$s kwam het spraakkanaal \"%2$s\" binnen.",
-		"left": "%1$s verlaatte het spraakkanaal \"%2$s\"."
+		"join": "$1 kwam het spraakkanaal \"$2\" binnen.",
+		"left": "$1 verlaatte het spraakkanaal \"$2\"."
 	},
 	"overview": {
 		"inaccurate": "Statistieken zijn mogelijk inaccuraat",
@@ -138,17 +140,17 @@
 			"loading": "Globale statistieken laden…"
 		},
 		"block": {
-			"header": "%s is momenteel geblokkeerd!",
-			"text": "Geblokkeerd van %1$s tot %2$s door %3$s met reden \"%4$s\".",
-			"noreason": "Geblokkeerd van %1$s tot %2$s door %3$s.",
-			"nofromtext": "Geblokkeerd tot %2$s door %3$s met reden \"%4$s\".",
-			"nofromnoreason": "Geblokkeerd tot %2$s door %3$s.",
+			"header": "$1 is momenteel geblokkeerd!",
+			"text": "Geblokkeerd van $1 tot $2 door $3 met reden \"$4\".",
+			"noreason": "Geblokkeerd van $1 tot $2 door $3.",
+			"nofromtext": "Geblokkeerd tot $2 door $3 met reden \"$4\".",
+			"nofromnoreason": "Geblokkeerd tot $2 door $3.",
 			"until_infinity": "het einde van de wereld"
 		},
 		"gblock": {
-			"header": "%s is momenteel globaal geblokkeerd!",
-			"text": "Geblokkeerd van %1$s tot %2$s door %3$s op %4$s met reden \"%5$s\".",
-			"noreason": "Geblokkeerd van %1$s tot %2$s door %3$s op %4$s.",
+			"header": "$1 is momenteel globaal geblokkeerd!",
+			"text": "Geblokkeerd van $1 tot $2 door $3 op $4 met reden \"$5\".",
+			"noreason": "Geblokkeerd van $1 tot $2 door $3 op $4.",
 			"disabled": "Dit account is momenteel uitgeschakeld!"
 		}
 	},
@@ -160,7 +162,7 @@
 			"editor": "Bewerker:",
 			"timestamp": "Bewerkingsdatum:",
 			"size": "Verschil:",
-			"bytes": "%s Bytes",
+			"bytes": "$1 {{PLURAL:$1|Byte|Bytes}}",
 			"comment": "Beschrijving:",
 			"tags": "Tags:",
 			"removed": "Verwijderd:",
@@ -172,23 +174,14 @@
 	"search": {
 		"page": "pagina",
 		"search": "zoeken",
-		"infopage": "Niet het gewenste resultaat? Gebruik %s voor een rechtstreekse link.",
-		"infosearch": "Niet het gewenste resultaat? Gebruik %1$s voor een rechtstreekse link of %2$s voor een lijst van alle overeenkomsten.",
+		"infopage": "Niet het gewenste resultaat? Gebruik $1 voor een rechtstreekse link.",
+		"infosearch": "Niet het gewenste resultaat? Gebruik $1 voor een rechtstreekse link of $2 voor een lijst van alle overeenkomsten.",
 		"category": {
 			"content": "Inhoud van deze categorie:",
 			"empty": "*Deze categorie is leeg*",
-			"pages": {
-				"default": "%s pagina's",
-				"1": "%s pagina"
-			},
-			"files": {
-				"default": "%s bestanden",
-				"1": "%s bestand"
-			},
-			"subcats": {
-				"default": "%s categorieën",
-				"1": "%s categorie"
-			}
+			"pages": "$1 {{PLURAL:$1|pagina|pagina's}}",
+			"files": "$1 {{PLURAL:$1|bestand|bestanden}}",
+			"subcats": "$1 {{PLURAL:$1|categorie|categorieën}}"
 		},
 		"special": "Inhoud van deze speciale pagina:",
 		"empty": "*Deze speciale pagina is leeg*"
@@ -197,10 +190,7 @@
 		"post": "bericht",
 		"main": "Discussies",
 		"image": "Bekijk afbeelding",
-		"votes": {
-			"default": "%s stemmen",
-			"1": "%s stem"
-		}
+		"votes": "$1 {{PLURAL:$1|stem|stemmen}}"
 	},
 	"invite": {
 		"bot": "Gebruik deze link om mij aan een andere server uit te nodigen:"
@@ -287,7 +277,7 @@
 		},
 		"private": "**Privé issue**",
 		"fixed": "Fixed versie:",
-		"more": "En %s meer.",
-		"total": "%s bugs opgelost"
+		"more": "En $1 {{PLURAL:$1|meer}}.",
+		"total": "$1 {{PLURAL:$1|bug|bugs}} opgelost"
 	}
 }

+ 53 - 90
i18n/pl.json

@@ -3,7 +3,9 @@
 		"Frisk",
 		"Rail"
 	],
-	"lang": "pl",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "pl-PL",
 	"aliases": {
 		"pomoc": "help",
@@ -14,15 +16,15 @@
 		"dyskusje": "discussion",
 		"dyskusja": "discussion"
 	},
-	"prefix": "prefiksem komend dla tego serwera jest `%s`. Możesz zmienić prefiks używając `%ssettings prefix`. Lista wszystkich komend jest dostępna przez użycie `%spomoc`.",
+	"prefix": "prefiksem komend dla tego serwera jest `$1`. Możesz zmienić prefiks używając `$1settings prefix`. Lista wszystkich komend jest dostępna przez użycie `$1pomoc`.",
 	"missingperm": "Brakuje mi uprawnień:",
-	"limit": "🚨 **Chwila moment! Osiągnięto limit!** 🚨\n\n%s, Twoja wiadomość zawiera zbyt wiele komend!",
-	"disclaimer": "Jestem małym botem, którego zadaniem jest linkowanie do różnych wiki na Gamepedii oraz Fandomie. Zostałem napisany w JavaScript przez %s.\n\n**Nie jestem oficjalny i nie mam żadnego wsparcia lub powiązania z Fandomem!**\n\nMożesz wesprzeć mnie na Patreon-ie:",
+	"limit": "🚨 **Chwila moment! Osiągnięto limit!** 🚨\n\n$1, Twoja wiadomość zawiera zbyt wiele komend!",
+	"disclaimer": "Jestem małym botem, którego zadaniem jest linkowanie do różnych wiki na Gamepedii oraz Fandomie. Zostałem napisany w JavaScript przez $1.\n\n**Nie jestem oficjalny i nie mam żadnego wsparcia lub powiązania z Fandomem!**\n\nMożesz wesprzeć mnie na Patreon-ie:",
 	"helpserver": "W przypadku pytań lub problemów, odwiedź mój serwer:",
 	"patreon": "to funkcja tylko dla patronów!\nMożesz wesprzeć moją pracę na Patreonie aby uzyskać dostęp do tej funkcji:",
 	"settings": {
 		"save_failed": "niestety, ustawienia nie mogły zostać zapisane, spróbuj ponownie później.",
-		"missing": "ten serwer nie został jeszcze skonfigurowany. Użyj %1$s oraz %2$s aby zmienić ustawienia.",
+		"missing": "ten serwer nie został jeszcze skonfigurowany. Użyj $1 oraz $2 aby zmienić ustawienia.",
 		"foundwikis": "Czy chodziło Ci o jedną z tych wiki?",
 		"current": "obecne ustawienia dla tego serwera:",
 		"channel current": "obecne ustawienia dla tego kanału:",
@@ -37,14 +39,14 @@
 		"langinvalid": "podany język nie jest wspierany!",
 		"langchanged": "zmieniono język serwera na:",
 		"channel langchanged": "zmieniono język kanału na:",
-		"langhelp": "Użyj `%s <język>` aby zmienić język.\nObecnie wspieranymi językami są:",
+		"langhelp": "Użyj `$1 <język>` aby zmienić język.\nObecnie wspieranymi językami są:",
 		"wikimissing": "domyślna wiki nie została ustawiona dla tego serwera!",
 		"wiki": "domyślną wiki tego serwera jest:",
 		"channel wiki": "domyślną wiki tego kanału jest:",
 		"wikiinvalid": "podaj poprawny link do wiki na Gamepedii lub Fandomie!",
 		"wikichanged": "zmieniono domyślną wiki dla tego serwera na:",
 		"channel wikichanged": "zmieniono domyślną wiki dla tego kanału na:",
-		"wikihelp": "Użyj `%s <link>` aby zmienić domyślną wiki.\nLink do wiki: `https://<wiki>.gamepedia.com/` lub `https://<wiki>.fandom.com/`",
+		"wikihelp": "Użyj `$1 <link>` aby zmienić domyślną wiki.\nLink do wiki: `https://<wiki>.gamepedia.com/` lub `https://<wiki>.fandom.com/`",
 		"prefix": "prefiks dla tego serwera:",
 		"prefixinvalid": "podany prefiks nie jest wspierany!",
 		"prefixchanged": "zmieniono prefiks dla tego serwera na:",
@@ -53,14 +55,14 @@
 			"channel inline": "linkowanie składnią wiki jest obecnie włączone dla tego kanału.",
 			"inlinechanged": "włączono linkowanie składnią wiki dla tego serwera.",
 			"channel inlinechanged": "włączono linkowanie składnią wiki dla tego kanału.",
-			"help": "Użyj `%1$s` aby wyłączyć linkowanie składnią wiki takiego jak `[[%2$s]]` czy `{{%2$s}}`."
+			"help": "Użyj `$1` aby wyłączyć linkowanie składnią wiki takiego jak `[[$2]]` czy `{{$2}}`."
 		},
 		"inline disabled": {
 			"inline": "linkowanie składnią wiki jest obecnie wyłączone dla tego serwera.",
 			"channel inline": "linkowanie składnią wiki jest obecnie wyłączone dla tego kanału.",
 			"inlinechanged": "wyłączono linkowanie składnią wiki dla tego serwera.",
 			"channel inlinechanged": "wyłączono linkowanie składnią wiki dla tego kanału.",
-			"help": "Użyj `%1$s` aby włączyć linkowanie składnią wiki takiego jak `[[%2$s]]` czy `{{%2$s}}`."
+			"help": "Użyj `$1` aby włączyć linkowanie składnią wiki takiego jak `[[$2]]` czy `{{$2}}`."
 		}
 	},
 	"pause": {
@@ -69,14 +71,14 @@
 	},
 	"voice": {
 		"text": "nadaję każdemu w kanale głosowym określoną rolę:",
-		"enable": "Użyj `%s` aby włączyć tę funkcję.",
-		"disable": "Użyj `%s` aby wyłączyć tę funkcję.",
+		"enable": "Użyj `$1` aby włączyć tę funkcję.",
+		"disable": "Użyj `$1` aby wyłączyć tę funkcję.",
 		"enabled": "włączono funkcję nadawania ról dla osób korzystających z czatów głosowych.",
 		"disabled": "wyłączono funkcję nadawania ról dla osób korzystających z czatów głosowych.",
 		"name": "nazwa kanału głosowego",
 		"channel": "Kanał głosowy",
-		"join": "%1$s dołączył do \"%2$s\".",
-		"left": "%1$s wyszedł z \"%2$s\"."
+		"join": "$1 dołączył do \"$2\".",
+		"left": "$1 wyszedł z \"$2\"."
 	},
 	"verification": {
 		"save_failed": "niestety, weryfikacja nie mogła zostać zapisana, spróbuj ponownie później.",
@@ -86,7 +88,7 @@
 		"max_entries": "osiągnięto maksymalną ilość weryfikacji.",
 		"no_role": "podaj rolę dla nowej weryfikacji.",
 		"current": "lista obecnych weryfikacji na tym serwerze:",
-		"current_selected": "to weryfikacja `%s` dla tego serwera:",
+		"current_selected": "to weryfikacja `$1` dla tego serwera:",
 		"missing": "na tym serwerze nie istnieją żadne weryfikacje na tę chwilę.",
 		"add_more": "Dodaj więcej weryfikacji:",
 		"delete_current": "Usuń tę weryfikację:",
@@ -102,8 +104,8 @@
 		"enabled": "włączone",
 		"disabled": "wyłączone",
 		"toggle": "(przełącz)",
-		"rename_no_permission": "**%s wymaga uprawnienia `Zarządzanie pseudonimami` do zmiany nazw użytkowników na wiki!**",
-		"role_too_high": "**Rola %1$s jest wyżej niż najwyższa rola %2$s, dlatego nie będzie możliwe jej ustawienie!**",
+		"rename_no_permission": "**$1 wymaga uprawnienia `Zarządzanie pseudonimami` do zmiany nazw użytkowników na wiki!**",
+		"role_too_high": "**Rola $1 jest wyżej niż najwyższa rola $2, dlatego nie będzie możliwe jej ustawienie!**",
 		"channel_max": "podano zbyt dużo kanałów.",
 		"channel_missing": "podany kanał nie istnieje.",
 		"role_max": "podano zbyt dużo ról.",
@@ -126,21 +128,21 @@
 		"failed_gblock": "**Sprawdzenie globalnych blokad nie powiodło się!**",
 		"failed_roles": "**Dodawanie ról nie powiodło się!**",
 		"failed_rename": "**Zmiana nazwy użytkownika nie powiodła się!**",
-		"audit_reason": "Zweryfikowano jako \"%s\"",
-		"user_missing": "Użytkownik wiki \"%s\" nie istnieje.",
-		"user_missing_reply": "konto zalinkowanego użytkownika wiki \"%s\" nie istnieje.",
-		"user_blocked": "**Konto użytkownika wiki %s jest zablokowane!**",
-		"user_blocked_reply": "konto zalinkowanego użytkownika wiki **\"%s\" jest zablokowane!**",
-		"user_gblocked": "**Konto użytkownika wiki %s jest globalnie zablokowane!**",
-		"user_gblocked_reply": "konto zalinkowanego użytkownika wiki **\"%s\" jest globalnie zablokowane!**",
-		"user_disabled": "**Konto użytkownika wiki %s jest zablokowane!**",
-		"user_disabled_reply": "konto zalinkowanego użytkownika wiki **\"%s\" jest wyłączone!**",
-		"user_failed": "Nazwa użytkownika Discord %1$s nie jest zgodna z nazwą podaną na profilu wiki %2$s.",
-		"user_failed_reply": "Twoja nazwa użytkownika na Discord nie pokrywa się z nazwą podaną na wiki \"%s\".",
-		"user_matches": "Twoja nazwa użytkownika na Discordzie %1$s pokrywa się z nazwą na wiki %2$s, ale nie kwalifikuje na żadną rolę.",
-		"user_matches_reply": "Twoja nazwa użytkownika Discord pokrywa się z nazwą użytkownika z wiki \"%s\", lecz nie kwalifikuje na żadną rolę.",
-		"user_verified": "Pomyślnie zweryfikowano użytkownika Discord %1$s jako użytkownika wiki %2$s.",
-		"user_verified_reply": "pomyślnie zweryfikowano Ciebie jako użytkownika wiki \"%s\".",
+		"audit_reason": "Zweryfikowano jako \"$1\"",
+		"user_missing": "Użytkownik wiki \"$1\" nie istnieje.",
+		"user_missing_reply": "konto zalinkowanego użytkownika wiki \"$1\" nie istnieje.",
+		"user_blocked": "**Konto użytkownika wiki $1 jest zablokowane!**",
+		"user_blocked_reply": "konto zalinkowanego użytkownika wiki **\"$1\" jest zablokowane!**",
+		"user_gblocked": "**Konto użytkownika wiki $1 jest globalnie zablokowane!**",
+		"user_gblocked_reply": "konto zalinkowanego użytkownika wiki **\"$1\" jest globalnie zablokowane!**",
+		"user_disabled": "**Konto użytkownika wiki $1 jest zablokowane!**",
+		"user_disabled_reply": "konto zalinkowanego użytkownika wiki **\"$1\" jest wyłączone!**",
+		"user_failed": "Nazwa użytkownika Discord $1 nie jest zgodna z nazwą podaną na profilu wiki $2.",
+		"user_failed_reply": "Twoja nazwa użytkownika na Discord nie pokrywa się z nazwą podaną na wiki \"$1\".",
+		"user_matches": "Twoja nazwa użytkownika na Discordzie $1 pokrywa się z nazwą na wiki $2, ale nie kwalifikuje na żadną rolę.",
+		"user_matches_reply": "Twoja nazwa użytkownika Discord pokrywa się z nazwą użytkownika z wiki \"$1\", lecz nie kwalifikuje na żadną rolę.",
+		"user_verified": "Pomyślnie zweryfikowano użytkownika Discord $1 jako użytkownika wiki $2.",
+		"user_verified_reply": "pomyślnie zweryfikowano Ciebie jako użytkownika wiki \"$1\".",
 		"user_renamed": "Nazwa użytkownika Discord została zmieniona.",
 		"discord": "Użytkownik Discord:",
 		"wiki": "Użytkownik Wiki:",
@@ -148,10 +150,10 @@
 		"notice": "Powiadomienie:",
 		"qualified": "Pozwala na otrzymanie ról:",
 		"qualified_error": "Pozwala na otrzymanie ról, których nie udało się dodać:",
-		"help_guide": "Podążaj za [tym poradnikiem](%s) w celu dodania nazwy użytkownika Discord na profil wiki.",
+		"help_guide": "Podążaj za [tym poradnikiem]($1) w celu dodania nazwy użytkownika Discord na profil wiki.",
 		"help_gamepedia": "https://help.gamepedia.com/Gamepedia_Help_Wiki:Discord_verification",
 		"help_fandom": "https://community.fandom.com/wiki/Special:VerifyUser",
-		"help_subpage": "Dodaj swoją nazwę użytkownika Discord (%s) do podstrony Discord na wiki:"
+		"help_subpage": "Dodaj swoją nazwę użytkownika Discord ($1) do podstrony Discord na wiki:"
 	},
 	"overview": {
 		"inaccurate": "Statystyki mogą być niedokładne",
@@ -230,17 +232,17 @@
 			"loading": "Ładowanie globalnych statystyk…"
 		},
 		"block": {
-			"header": "%s jest obecnie zablokowany(-na)!",
-			"text": "Zablokowano %1$s do %2$s przez %3$s z powodem \"%4$s\".",
-			"noreason": "Zablokowano %1$s do %2$s przez %3$s.",
-			"nofromtext": "Zablokowano do %2$s przez %3$s z powodem \"%4$s\".",
-			"nofromnoreason": "Zablokowano do %2$s przez %3$s.",
+			"header": "$1 jest obecnie zablokowany(-na)!",
+			"text": "Zablokowano $1 do $2 przez $3 z powodem \"$4\".",
+			"noreason": "Zablokowano $1 do $2 przez $3.",
+			"nofromtext": "Zablokowano do $2 przez $3 z powodem \"$4\".",
+			"nofromnoreason": "Zablokowano do $2 przez $3.",
 			"until_infinity": "do końca wszechświata"
 		},
 		"gblock": {
-			"header": "%s jest zablokowany(-na) globalnie!",
-			"text": "Zablokowano %1$s do %2$s przez %3$s na %4$s z podanym powodem \"%5$s\".",
-			"noreason": "Zablokowano %1$s do %2$s przez %3$s na %4$s.",
+			"header": "$1 jest zablokowany(-na) globalnie!",
+			"text": "Zablokowano $1 do $2 przez $3 na $4 z podanym powodem \"$5\".",
+			"noreason": "Zablokowano $1 do $2 przez $3 na $4.",
 			"disabled": "To konto jest obecnie wyłączone!"
 		}
 	},
@@ -252,7 +254,7 @@
 			"editor": "Autor:",
 			"timestamp": "Data edycji:",
 			"size": "Wielkość zmiany:",
-			"bytes": "%s bajtów",
+			"bytes": "$1 {{PLURAL:$1|bajt|bajty|bajtów}}",
 			"comment": "Komentarz:",
 			"tags": "Tagi:",
 			"removed": "Usunięto:",
@@ -264,63 +266,24 @@
 	"search": {
 		"page": "strona",
 		"search": "szukaj",
-		"infopage": "Nie to czego szukałeś? Użyj %s dla bezpośredniego linku.",
-		"infosearch": "Nie to czego szukałeś? Użyj %1$s dla bezpośredniego linku lub %2$s dla listy wszystkich wyników.",
+		"infopage": "Nie to czego szukałeś? Użyj $1 dla bezpośredniego linku.",
+		"infosearch": "Nie to czego szukałeś? Użyj $1 dla bezpośredniego linku lub $2 dla listy wszystkich wyników.",
 		"category": {
 			"content": "Zawartość tej kategorii:",
 			"empty": "*Ta kategoria jest pusta*",
-			"pages": {
-				"default": "%s stron",
-				"1": "%s strona",
-				"*2": "%s strony",
-				"*3": "%s strony",
-				"*4": "%s strony",
-				"*12": "%s stron",
-				"*13": "%s stron",
-				"*14": "%s stron"
-			},
-			"files": {
-				"default": "%s plików",
-				"1": "%s plik",
-				"*2": "%s pliki",
-				"*3": "%s pliki",
-				"*4": "%s pliki",
-				"*12": "%s plików",
-				"*13": "%s plików",
-				"*14": "%s plików"
-			},
-			"subcats": {
-				"default": "%s kategorii",
-				"1": "%s kategoria",
-				"*2": "%s katerorie",
-				"*3": "%s katerorie",
-				"*4": "%s katerorie",
-				"*12": "%s kategorii",
-				"*13": "%s kategorii",
-				"*14": "%s kategorii"
-			}
+			"pages": "$1 {{PLURAL:$1|strona|strony|stron}}",
+			"files": "$1 {{PLURAL:$1|plik|pliki|plików}}",
+			"subcats": "$1 {{PLURAL:$1|kategoria|katerorie|kategorii}}"
 		},
 		"special": "Zawartość tej strony specjalnej:",
 		"empty": "*Ta strona specjalna jest pusta*",
-		"results": {
-			"default": "%s wszystkich wyników",
-			"1": "%s wynik"
-		}
+		"results": "$1 {{PLURAL:$1|wynik|wyniki|wszystkich wyników}}"
 	},
 	"discussion": {
 		"post": "post",
 		"main": "Dyskusje",
 		"image": "Zobacz zdjęcie",
-		"votes": {
-			"default": "%s głosów",
-			"1": "%s głos",
-			"*2": "%s głosy",
-			"*3": "%s głosy",
-			"*4": "%s głosy",
-			"*12": "%s głosów",
-			"*13": "%s głosów",
-			"*14": "%s głosów"
-		}
+		"votes": "$1 {{PLURAL:$1|głos|głosy|głosów}}"
 	},
 	"invite": {
 		"bot": "Użyj tego linku aby zaprosić mnie na inny serwer:"
@@ -412,7 +375,7 @@
 		},
 		"private": "**Private Issue**",
 		"fixed": "Naprawione w wersji:",
-		"more": "Oraz %s innych.",
-		"total": "naprawiono %s błędów"
+		"more": "Oraz $1 {{PLURAL:$1|inny|inne|innych}}.",
+		"total": "naprawiono $1 {{PLURAL:$1|błąd|błędy|błędów}}"
 	}
 }

+ 51 - 65
i18n/pt.json

@@ -4,7 +4,9 @@
 		"Jhonabf",
 		"dr03ramos"
 	],
-	"lang": "pt",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "pt-PT",
 	"aliases": {
 		"ajuda": "help",
@@ -15,15 +17,15 @@
 		"discussions": "discussion",
 		"discussão": "discussion"
 	},
-	"prefix": "o prefixo deste servidor é `%s`. Você pode alterar o prefixo com `%ssettings prefix`. Para uma lista de todos os comandos, veja `%shelp`.",
+	"prefix": "o prefixo deste servidor é `$1`. Você pode alterar o prefixo com `$1settings prefix`. Para uma lista de todos os comandos, veja `$1help`.",
 	"missingperm": "eu estou perdendo algumas permissões para este comando:",
-	"limit": "🚨 **Pare, você atingiu um limite!** 🚨\n\n%s, sua mensagem continha muitos comandos!",
-	"disclaimer": "Eu sou um pequeno robô com a tarefa de vincular a Wikis da Gamepedia e Fandom. %s me escreveu em JavaScript.\n\n**Eu não sou afiliado com a Fandom e sou uma ferramenta não oficial!**\n\nVocê pode me apoiar no Patreon:",
+	"limit": "🚨 **Pare, você atingiu um limite!** 🚨\n\n$1, sua mensagem continha muitos comandos!",
+	"disclaimer": "Eu sou um pequeno robô com a tarefa de vincular a Wikis da Gamepedia e Fandom. $1 me escreveu em JavaScript.\n\n**Eu não sou afiliado com a Fandom e sou uma ferramenta não oficial!**\n\nVocê pode me apoiar no Patreon:",
 	"helpserver": "Para dúvidas e problemas, visite o meu servidor de suporte:",
 	"patreon": "esse é um recurso exclusivo do Patreon!\nVocê pode me apoiar no Patreon para obter acesso a esse recurso:",
 	"settings": {
 		"save_failed": "infelizmente as configurações não puderam ser salvas, por favor, tente novamente mais tarde.",
-		"missing": "este servidor ainda não está configurado. Use %1$s e %2$s para alterar as configurações.",
+		"missing": "este servidor ainda não está configurado. Use $1 e $2 para alterar as configurações.",
 		"foundwikis": "Você quer dizer algum dessas wikis?",
 		"current": "estas são as configurações atuais para este servidor:",
 		"channel current": "estas são as configurações atuais deste canal:",
@@ -37,14 +39,14 @@
 		"langinvalid": "o idioma especificado não é suportado!",
 		"langchanged": "você alterou o idioma deste servidor para:",
 		"channel langchanged": "você alterou o idioma deste canal para:",
-		"langhelp": "Use `%s <idioma>` para mudar o idioma.\nIdiomas atualmente suportados são:",
+		"langhelp": "Use `$1 <idioma>` para mudar o idioma.\nIdiomas atualmente suportados são:",
 		"wikimissing": "nenhuma wiki padrão está configurado para este servidor ainda!",
 		"wiki": "a wiki padrão para este servidor é:",
 		"channel wiki": "a wiki padrão para este canal é:",
 		"wikiinvalid": "por favor, forneça um link válido para uma wiki da Gamepedia ou Fandom!",
 		"wikichanged": "você mudou a wiki padrão para este servidor para:",
 		"channel wikichanged": "você mudou a wiki padrão para este canal para:",
-		"wikihelp": "Use `%s <link>` para alterar a wiki padrão.\nLink para a wiki: `https://<wiki>.gamepedia.com/` ou `https://<wiki>.fandom.com/`",
+		"wikihelp": "Use `$1 <link>` para alterar a wiki padrão.\nLink para a wiki: `https://<wiki>.gamepedia.com/` ou `https://<wiki>.fandom.com/`",
 		"prefix": "o prefixo deste servidor é:",
 		"prefixinvalid": "o prefixo especificado não é suportado!",
 		"prefixchanged": "você mudou o prefixo deste servidor para:"
@@ -55,14 +57,14 @@
 	},
 	"voice": {
 		"text": "Eu tento dar a todos em um canal de voz um papel específico:",
-		"enable": "Use `%s` para habilitar esta função.",
-		"disable": "Use `%s` desativar esta função.",
+		"enable": "Use `$1` para habilitar esta função.",
+		"disable": "Use `$1` desativar esta função.",
 		"enabled": "você ativou a função para adicionar funções aos canais de voz.",
 		"disabled": "você desabilitou a função para adicionar funções aos canais de voz.",
 		"name": "nome do canal de voz",
 		"channel": "Canal de voz",
-		"join": "%1$s juntou-se ao canal de voz \"%2$s\".",
-		"left": "%1$s deixou o canal de voz \"%2$s\"."
+		"join": "$1 juntou-se ao canal de voz \"$2\".",
+		"left": "$1 deixou o canal de voz \"$2\"."
 	},
 	"verification": {
 		"save_failed": "infelizmente a verificação não pôde ser salva, tente novamente mais tarde.",
@@ -72,7 +74,7 @@
 		"max_entries": "você já atingiu a quantidade máxima de verificações.",
 		"no_role": "por favor forneça um cargo para a nova verificação.",
 		"current": "estas são as verificações disponíveis para este servidor:",
-		"current_selected": "esta é a verificação `%s` para este servidor:",
+		"current_selected": "esta é a verificação `$1` para este servidor:",
 		"missing": "ainda não há verificações para este servidor.",
 		"add_more": "Para adicionar mais verificações:",
 		"delete_current": "Para excluir esta verificação:",
@@ -88,8 +90,8 @@
 		"enabled": "ativado",
 		"disabled": "desativado",
 		"toggle": "(alternar)",
-		"rename_no_permission": "**%s não tem a permissão de `Gerenciar apelidos` para substituir o apelido pelos nomes de usuário da wiki!**",
-		"role_too_high": "**O cargo %1$s é muito alto para o %2$s atribuí-la!**",
+		"rename_no_permission": "**$1 não tem a permissão de `Gerenciar apelidos` para substituir o apelido pelos nomes de usuário da wiki!**",
+		"role_too_high": "**O cargo $1 é muito alto para o $2 atribuí-la!**",
 		"channel_max": "muitos canais fornecidos.",
 		"channel_missing": "o canal fornecido não existe.",
 		"role_max": "muitos cargos fornecidos.",
@@ -112,21 +114,21 @@
 		"failed_gblock": "**Falha na verificação de bloqueios globais!**",
 		"failed_roles": "**A adição de cargos falhou!**",
 		"failed_rename": "**A alteração do apelido falhou!**",
-		"audit_reason": "Verificado como \"%s\"",
-		"user_missing": "O usuário \"%s\" não existe na wiki.",
-		"user_missing_reply": "seu usuário da wiki \"%s\" vinculado não existe.",
-		"user_blocked": "**O usuário %s está bloqueado na wiki!**",
-		"user_blocked_reply": "seu usuário da wiki **\"%s\" vinculado está bloqueado!**",
-		"user_gblocked": "**O usuário %s está bloqueado globalmente na wiki!**",
-		"user_gblocked_reply": "seu usuário da wiki **\"%s\" vinculado está bloqueado globalmente!**",
-		"user_disabled": "**O usuário %s está desabilitado na wiki!**",
-		"user_disabled_reply": "seu usuário da wiki **\"%s\" vinculado está desabilitado!**",
-		"user_failed": "O usuário %1$s do Discord não corresponde ao usuário %2$s da wiki.",
-		"user_failed_reply": "seu nome de usuário do Discord não corresponde ao usuário \"%s\" da wiki.",
-		"user_matches": "O usuário %1$s do Discord corresponde ao usuário %2$s da wiki, mas não atende aos requisitos mínimos para nenhum cargo.",
-		"user_matches_reply": "seu nome de usuário do Discord corresponde ao usuário \"%s\" da wiki, mas não atende aos requisitos mínimos para nenhum cargo.",
-		"user_verified": "O usuário %1$s do Discord foi verificado com sucesso como o usuário %2$s da wiki.",
-		"user_verified_reply": "você foi verificado com sucesso como o usuário \"%s\" da wiki.",
+		"audit_reason": "Verificado como \"$1\"",
+		"user_missing": "O usuário \"$1\" não existe na wiki.",
+		"user_missing_reply": "seu usuário da wiki \"$1\" vinculado não existe.",
+		"user_blocked": "**O usuário $1 está bloqueado na wiki!**",
+		"user_blocked_reply": "seu usuário da wiki **\"$1\" vinculado está bloqueado!**",
+		"user_gblocked": "**O usuário $1 está bloqueado globalmente na wiki!**",
+		"user_gblocked_reply": "seu usuário da wiki **\"$1\" vinculado está bloqueado globalmente!**",
+		"user_disabled": "**O usuário $1 está desabilitado na wiki!**",
+		"user_disabled_reply": "seu usuário da wiki **\"$1\" vinculado está desabilitado!**",
+		"user_failed": "O usuário $1 do Discord não corresponde ao usuário $2 da wiki.",
+		"user_failed_reply": "seu nome de usuário do Discord não corresponde ao usuário \"$1\" da wiki.",
+		"user_matches": "O usuário $1 do Discord corresponde ao usuário $2 da wiki, mas não atende aos requisitos mínimos para nenhum cargo.",
+		"user_matches_reply": "seu nome de usuário do Discord corresponde ao usuário \"$1\" da wiki, mas não atende aos requisitos mínimos para nenhum cargo.",
+		"user_verified": "O usuário $1 do Discord foi verificado com sucesso como o usuário $2 da wiki.",
+		"user_verified_reply": "você foi verificado com sucesso como o usuário \"$1\" da wiki.",
 		"user_renamed": "Seus apelidos do Discord foram alterados para os nomes de usuário da wiki.",
 		"discord": "Usuário do Discord:",
 		"wiki": "Usuário da wiki:",
@@ -134,10 +136,10 @@
 		"notice": "Aviso:",
 		"qualified": "Qualificado para o cargo:",
 		"qualified_error": "Não foi possível adicionar, mas está qualificado para o cargo:",
-		"help_guide": "Siga [esse guia](%s) para associar seu nome de usuário do Discord ao seu usuário na wiki:",
+		"help_guide": "Siga [esse guia]($1) para associar seu nome de usuário do Discord ao seu usuário na wiki:",
 		"help_gamepedia": "https://help.gamepedia.com/Gamepedia_Help_Wiki:Discord_verification",
 		"help_fandom": "https://community.fandom.com/wiki/Special:VerifyUser",
-		"help_subpage": "Adicione seu nome de usuário do Discord (%s) para sua subpágina do Discord na wiki:"
+		"help_subpage": "Adicione seu nome de usuário do Discord ($1) para sua subpágina do Discord na wiki:"
 	},
 	"overview": {
 		"inaccurate": "Estatísticas podem ser imprecisas",
@@ -215,17 +217,17 @@
 			"loading": "Carregando estatísticas globais…"
 		},
 		"block": {
-			"header": "%s está atualmente bloqueado!",
-			"text": "Bloqueado em %1$s até %2$s por %3$s com motivo \"%4$s\".",
-			"noreason": "Bloqueado em %1$s até %2$s por %3$s.",
-			"nofromtext": "Bloqueado até %2$s por %3$s com motivo \"%4$s\".",
-			"nofromnoreason": "Bloqueado até %2$s por %3$s.",
+			"header": "$1 está atualmente bloqueado!",
+			"text": "Bloqueado em $1 até $2 por $3 com motivo \"$4\".",
+			"noreason": "Bloqueado em $1 até $2 por $3.",
+			"nofromtext": "Bloqueado até $2 por $3 com motivo \"$4\".",
+			"nofromnoreason": "Bloqueado até $2 por $3.",
 			"until_infinity": "o fim de todos os dias"
 		},
 		"gblock": {
-			"header": "%s está atualmente bloqueado globalmente!",
-			"text": "Bloqueado em %1$s até %2$s por %3$s em %4$s com motivo \"%5$s\".",
-			"noreason": "Bloqueado em %1$s até %2$s por %3$s em %4$s.",
+			"header": "$1 está atualmente bloqueado globalmente!",
+			"text": "Bloqueado em $1 até $2 por $3 em $4 com motivo \"$5\".",
+			"noreason": "Bloqueado em $1 até $2 por $3 em $4.",
 			"disabled": "Esta conta está desativada no momento!"
 		}
 	},
@@ -237,7 +239,7 @@
 			"editor": "Editor:",
 			"timestamp": "Editar data:",
 			"size": "Diferença:",
-			"bytes": "%s Bytes",
+			"bytes": "$1 {{PLURAL:$1|Byte|Bytes}}",
 			"comment": "Comment:",
 			"tags": "Tags:",
 			"removed": "Removido:",
@@ -249,39 +251,23 @@
 	"search": {
 		"page": "página",
 		"search": "pesquisar",
-		"infopage": "Não é o resultado correto? Use %s para um link direto.",
-		"infosearch": "Não é o resultado correto? Use %1$s para um link direto ou %2$s para uma lista de todos os acessos.",
+		"infopage": "Não é o resultado correto? Use $1 para um link direto.",
+		"infosearch": "Não é o resultado correto? Use $1 para um link direto ou $2 para uma lista de todos os acessos.",
 		"category": {
 			"content": "Conteúdo  nessa categoria:",
 			"empty": "*Esta categoria está vazia*",
-			"pages": {
-				"default": "%s páginas",
-				"1": "%s página"
-			},
-			"files": {
-				"default": "%s arquivos",
-				"1": "%s arquivo"
-			},
-			"subcats": {
-				"default": "%s categorias",
-				"1": "%s categoria"
-			}
+			"pages": "$1 {{PLURAL:$1|página|páginas}}",
+			"files": "$1 {{PLURAL:$1|arquivo|arquivos}}",
+			"subcats": "$1 {{PLURAL:$1|categoria|categorias}}"
 		},
 		"special": "Conteúdo desta página especial:",
-		"empty": "*Esta página especial está vazia*",
-		"results": {
-			"default": "%s total results",
-			"1": "%s total result"
-		}
+		"empty": "*Esta página especial está vazia*"
 	},
 	"discussion": {
 		"post": "post",
 		"main": "Discussãos",
 		"image": "Ver imagem",
-		"votes": {
-			"default": "%s votos",
-			"1": "%s voto"
-		}
+		"votes": "$1 {{PLURAL:$1|voto|votos}}"
 	},
 	"invite": {
 		"bot": "Use este link para me convidar para outro servidor:"
@@ -374,7 +360,7 @@
 		},
 		"private": "**Private Issue**",
 		"fixed": "Versão corrigida:",
-		"more": "E %s mais.",
-		"total": "%s problemas corrigidos"
+		"more": "E $1 {{PLURAL:$1|mais}}.",
+		"total": "$1 {{PLURAL:$1|problema|problemas}} corrigidos"
 	}
 }

+ 48 - 59
i18n/ru.json

@@ -3,7 +3,9 @@
 		"Hosh",
 		"AttemptToCallNil"
 	],
-	"lang": "ru",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "ru-RU",
 	"aliases": {
 		"страница": "page",
@@ -11,15 +13,15 @@
 		"🎲": "random",
 		"discussions": "discussion"
 	},
-	"prefix": "префикс для этого сервера `%s`. Вы можете изменить префикс, введя команду `%ssettings prefix`. Чтобы получить список всех команд, введите `%shelp`.",
+	"prefix": "префикс для этого сервера `$1`. Вы можете изменить префикс, введя команду `$1settings prefix`. Чтобы получить список всех команд, введите `$1help`.",
 	"missingperm": "Я потерял какие-то разрешения для выполнения команды:",
-	"limit": "🚨 **Стой, ты уперся в лимит!** 🚨\n\n%s, твое сообщение содержит очень много команд!",
-	"disclaimer": "Я небольшой бот для отправки ссылок с Gamepedia и Fandom вики. %s меня написали на JavaScript.\n\n**Я не связан с Fandom и являюсь неофициальным инструментом!**\n\nВы можете поддержать меня на Patreon:",
+	"limit": "🚨 **Стой, ты уперся в лимит!** 🚨\n\n$1, твое сообщение содержит очень много команд!",
+	"disclaimer": "Я небольшой бот для отправки ссылок с Gamepedia и Fandom вики. $1 меня написали на JavaScript.\n\n**Я не связан с Fandom и являюсь неофициальным инструментом!**\n\nВы можете поддержать меня на Patreon:",
 	"helpserver": "По вопросам и проблемам, пожалуйста, посетите мой сервер поддержки:",
 	"patreon": "эта функция для подписчиков Patreon!\nВы можете поддержать нас на Patreon для доступа к этой функции:",
 	"settings": {
 		"save_failed": "к сожалению, настройки не могут быть сохранены, пожалуйста попробуйте позже.",
-		"missing": "этот сервер еще не настроен. Используйте %1$s и %2$s, чтобы изменить настройки.",
+		"missing": "этот сервер еще не настроен. Используйте $1 и $2, чтобы изменить настройки.",
 		"foundwikis": "Вы имеете в виду какие-либо из этих вики?",
 		"current": "вот настройки сервера на данный момент:",
 		"channel current": "вот настройки этого канала на данный момент:",
@@ -33,14 +35,14 @@
 		"langinvalid": "указанный язык не поддерживается!",
 		"langchanged": "вы сменили язык этого сервера на:",
 		"channel langchanged": "вы сменили язык этого канала на:",
-		"langhelp": "Используйте `%s <язык>` для смены языка.\nВ настоящее время поддерживаются языки:",
+		"langhelp": "Используйте `$1 <язык>` для смены языка.\nВ настоящее время поддерживаются языки:",
 		"wikimissing": "вики по умолчанию для этого сервера не установлены!",
 		"wiki": "вики по умолчанию для этого сервера:",
 		"channel wiki": "вики по умолчанию для этого канала:",
 		"wikiinvalid": "пожалуйста, предоставьте действительную ссылку на Gamepedia или Fandom вики!",
 		"wikichanged": "вы изменили вики по умолчанию для этого сервера:",
 		"channel wikichanged": "вы изменили вики по умолчанию для этого канала на:",
-		"wikihelp": "Используйте `%s <ссылка>` чтобы изменить вики по умолчанию.\nСсылка на вики: `https://<вики>.gamepedia.com/` или `https://<вики>.fandom.com/`",
+		"wikihelp": "Используйте `$1 <ссылка>` чтобы изменить вики по умолчанию.\nСсылка на вики: `https://<вики>.gamepedia.com/` или `https://<вики>.fandom.com/`",
 		"prefix": "префикс для этого сервера:",
 		"prefixinvalid": "указанный префикс не поддерживается!",
 		"prefixchanged": "вы изменили префикс для этого сервера на:"
@@ -51,14 +53,14 @@
 	},
 	"voice": {
 		"text": "Я пытаюсь дать каждому в голосовом канале определенную роль:",
-		"enable": "Введите `%s` для включения этой функции.",
-		"disable": "Введите `%s` для выключения этой функции.",
+		"enable": "Введите `$1` для включения этой функции.",
+		"disable": "Введите `$1` для выключения этой функции.",
 		"enabled": "Вы включили функцию добавления ролей для голосовых каналов.",
 		"disabled": "Вы выключили функцию добавления ролей для голосовых каналов.",
 		"name": "название голосового канала",
 		"channel": "Голосовой канал",
-		"join": "%1$s подключился к голосовому каналу \"%2$s\".",
-		"left": "%1$s покинул голосовой канал \"%2$s\"."
+		"join": "$1 подключился к голосовому каналу \"$2\".",
+		"left": "$1 покинул голосовой канал \"$2\"."
 	},
 	"verification": {
 		"save_failed": "к сожалению, проверка не сохранена, повторите попытку позже.",
@@ -68,7 +70,7 @@
 		"max_entries": "Вы уже достигли максимального количества проверок",
 		"no_role": "пожалуйста, укажите роль для новой проверки.",
 		"current": "текущие проверки для этого сервера:",
-		"current_selected": "это проверка `%s` для этого сервера:",
+		"current_selected": "это проверка `$1` для этого сервера:",
 		"missing": "У этого сервера пока что нет проверок.",
 		"add_more": "Добавить еще проверок:",
 		"delete_current": "Удалить проверку:",
@@ -84,8 +86,8 @@
 		"enabled": "enabled",
 		"disabled": "disabled",
 		"toggle": "(toggle)",
-		"rename_no_permission": "**%s is missing the `Manage Nicknames` permission to force wiki usernames!**",
-		"role_too_high": "**Роль %1$s слишком высока %2$s для назначения!**",
+		"rename_no_permission": "**$1 is missing the `Manage Nicknames` permission to force wiki usernames!**",
+		"role_too_high": "**Роль $1 слишком высока $2 для назначения!**",
 		"channel_max": "Вы достигли максимального кол-ва каналов.",
 		"channel_missing": "такого канала не существует.",
 		"role_max": "Вы достигли максимального кол-ва ролей.",
@@ -108,21 +110,21 @@
 		"failed_gblock": "**Не удалось проверить глобальный бан!**",
 		"failed_roles": "**Не удалось добавить роль!**",
 		"failed_rename": "**Changing the nickname failed!**",
-		"audit_reason": "Проверено как \"%s\"",
-		"user_missing": "Wiki-пользователь \"%s\" не существует.",
-		"user_missing_reply": "Ваш связанный wiki-пользователь \"%s\" не существует.",
-		"user_blocked": "**Wiki-пользователь %s заблокирован.**",
-		"user_blocked_reply": "Ваш связанный wiki-пользователь **\"%s\" заблокирован!**",
-		"user_gblocked": "**Wiki-пользователь %s имеет глобальный бан!**",
-		"user_gblocked_reply": "Ваш связанный Wiki-пользователь **\"%s\" имеет глобальный бан!**",
-		"user_disabled": "**Wiki-пользователь %s отключен!**",
-		"user_disabled_reply": "Ваш связанный wiki-пользователь **\"%s\" отключен!**",
-		"user_failed": "Discord-пользователь %1$s не совпадает с wiki-пользователем %2$s.",
-		"user_failed_reply": "Ваш ник в дискорде не совпадает с wiki-пользователем \"%s\".",
-		"user_matches": "Discord-пользователь %1$s совпадает с wiki-пользователем %2$s, но не соответствует требованиям для любых ролей.",
-		"user_matches_reply": "Ваш ник в дискорде совпадает с wiki-пользователем\"%s\", но не соответствует требованиям для любых ролей.",
-		"user_verified": "Discord-пользователь %1$s успешно подтвержден как пользователь вики %2$s.",
-		"user_verified_reply": "Вы были подтверждены как пользователь вики \"%s\".",
+		"audit_reason": "Проверено как \"$1\"",
+		"user_missing": "Wiki-пользователь \"$1\" не существует.",
+		"user_missing_reply": "Ваш связанный wiki-пользователь \"$1\" не существует.",
+		"user_blocked": "**Wiki-пользователь $1 заблокирован.**",
+		"user_blocked_reply": "Ваш связанный wiki-пользователь **\"$1\" заблокирован!**",
+		"user_gblocked": "**Wiki-пользователь $1 имеет глобальный бан!**",
+		"user_gblocked_reply": "Ваш связанный Wiki-пользователь **\"$1\" имеет глобальный бан!**",
+		"user_disabled": "**Wiki-пользователь $1 отключен!**",
+		"user_disabled_reply": "Ваш связанный wiki-пользователь **\"$1\" отключен!**",
+		"user_failed": "Discord-пользователь $1 не совпадает с wiki-пользователем $2.",
+		"user_failed_reply": "Ваш ник в дискорде не совпадает с wiki-пользователем \"$1\".",
+		"user_matches": "Discord-пользователь $1 совпадает с wiki-пользователем $2, но не соответствует требованиям для любых ролей.",
+		"user_matches_reply": "Ваш ник в дискорде совпадает с wiki-пользователем\"$1\", но не соответствует требованиям для любых ролей.",
+		"user_verified": "Discord-пользователь $1 успешно подтвержден как пользователь вики $2.",
+		"user_verified_reply": "Вы были подтверждены как пользователь вики \"$1\".",
 		"user_renamed": "Their Discord nickname has been changed to their wiki username.",
 		"discord": "Discord-пользователь:",
 		"wiki": "Wiki-пользователь:",
@@ -130,7 +132,7 @@
 		"notice": "Заметка:",
 		"qualified": "Оценен для:",
 		"qualified_error": "Оценен, но не может быть добавлен:",
-		"help_guide": "Изучите [этот гайд](%s) для добавления пользователя Discord в свой профиль:",
+		"help_guide": "Изучите [этот гайд]($1) для добавления пользователя Discord в свой профиль:",
 		"help_gamepedia": "https://help.gamepedia.com/Gamepedia_Help_Wiki:Discord_verification",
 		"help_fandom": "https://community.fandom.com/wiki/Special:VerifyUser"
 	},
@@ -210,17 +212,17 @@
 			"loading": "Загрузка глобальной статистики…"
 		},
 		"block": {
-			"header": "%s сейчас заблокирован!",
-			"text": "Заблокирован с %1$s до %2$s пользователем %3$s с причиной \"%4$s\".",
-			"noreason": "Заблокирован с %1$s до %2$s пользователем %3$s.",
-			"nofromtext": "Заблокирован до %2$s пользователем %3$s с причиной \"%4$s\".",
-			"nofromnoreason": "Заблокирован до %2$s пользователем %3$s.",
+			"header": "$1 сейчас заблокирован!",
+			"text": "Заблокирован с $1 до $2 пользователем $3 с причиной \"$4\".",
+			"noreason": "Заблокирован с $1 до $2 пользователем $3.",
+			"nofromtext": "Заблокирован до $2 пользователем $3 с причиной \"$4\".",
+			"nofromnoreason": "Заблокирован до $2 пользователем $3.",
 			"until_infinity": "навсегда"
 		},
 		"gblock": {
-			"header": "%s в настоящее время глобально заблокирован!",
-			"text": "Заблокирован на %1$s до %2$s пользователем %3$s на вики %4$s с причиной \"%5$s\".",
-			"noreason": "Заблокирован на %1$s до %2$s пользователем %3$s на вики %4$s.",
+			"header": "$1 в настоящее время глобально заблокирован!",
+			"text": "Заблокирован на $1 до $2 пользователем $3 на вики $4 с причиной \"$5\".",
+			"noreason": "Заблокирован на $1 до $2 пользователем $3 на вики $4.",
 			"disabled": "Этот аккаунт в настоящее время отключен!"
 		}
 	},
@@ -232,7 +234,7 @@
 			"editor": "Редактор:",
 			"timestamp": "Дата редактирования:",
 			"size": "Отличие:",
-			"bytes": "%s Байтов",
+			"bytes": "$1 {{PLURAL:$1|Байтов}}",
 			"comment": "Комментарий:",
 			"tags": "Тэги:",
 			"removed": "Удалено:",
@@ -244,23 +246,14 @@
 	"search": {
 		"page": "страница",
 		"search": "поиск",
-		"infopage": "Неверный результат? Используйте %s для прямых ссылок.",
-		"infosearch": "Неверный результат? Используйте %1$s для прямых ссылок или %2$s для получения всех результатов.",
+		"infopage": "Неверный результат? Используйте $1 для прямых ссылок.",
+		"infosearch": "Неверный результат? Используйте $1 для прямых ссылок или $2 для получения всех результатов.",
 		"category": {
 			"content": "Содержимое этой категории:",
 			"empty": "*Эта категория пуста*",
-			"pages": {
-				"default": "%s страниц",
-				"1": "%s страница"
-			},
-			"files": {
-				"default": "%s файлов",
-				"1": "%s файл"
-			},
-			"subcats": {
-				"default": "%s категорий",
-				"1": "%s категория"
-			}
+			"pages": "$1 {{PLURAL:$1|страница|страниц}}",
+			"files": "$1 {{PLURAL:$1|файл|файлов}}",
+			"subcats": "$1 {{PLURAL:$1|категория|категорий}}"
 		},
 		"special": "Содержание этой спецтраницы:",
 		"empty": "*Эта спецстраница пуста*"
@@ -269,10 +262,7 @@
 		"post": "пост",
 		"main": "Дискуссии",
 		"image": "Посмотреть картинку",
-		"votes": {
-			"default": "%s голосов",
-			"1": "%s голос"
-		}
+		"votes": "$1 {{PLURAL:$1|голос|голосов}}"
 	},
 	"invite": {
 		"bot": "Используйте эту ссылку, чтобы пригласить меня на другой сервер:"
@@ -355,7 +345,6 @@
 		},
 		"private": "**Конфиденциальный отчёт об ошибке**",
 		"fixed": "Исправлено в версии:",
-		"more": "и ещё %s ошибок.",
-		"total": "%s issues fixed"
+		"more": "и ещё $1 {{PLURAL:$1|ошибок}}."
 	}
 }

+ 31 - 42
i18n/tr.json

@@ -2,7 +2,9 @@
 	"__translator": [
 		"Doğukan Karakaş"
 	],
-	"lang": "tr",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "tr-TR",
 	"aliases": {
 		"bilgi": "info",
@@ -13,15 +15,15 @@
 		"discussions": "discussion",
 		"tartışma": "discussion"
 	},
-	"prefix": "sunucudaki önekim `%s`. Bunu `%ssettings prefix` ile değiştirebilirsin. Komutların bütün listesi için `%syardım`.",
+	"prefix": "sunucudaki önekim `$1`. Bunu `$1settings prefix` ile değiştirebilirsin. Komutların bütün listesi için `$1yardım`.",
 	"missingperm": "Bu komutu uygulamak için ihtiyacım olan birkaç izin eksik:",
-	"limit": "🚨 **Dur, limite ulaştın!** 🚨\n\n%s, mesajın çok fazla komut içeriyor!",
-	"disclaimer": "Ben, Gamepedia ve Fandom Wikilerinin bağlantılarını göndermek ile görevlendirilmiş küçük bir botum. %s, beni JavaScript kullanarak yazdı.\n\n**Fandom ile resmi bir bağım yok, ben bir gayri resmi aracım!!**\n\nBeni Patreon'da da destekleyebilirsiniz:",
+	"limit": "🚨 **Dur, limite ulaştın!** 🚨\n\n$1, mesajın çok fazla komut içeriyor!",
+	"disclaimer": "Ben, Gamepedia ve Fandom Wikilerinin bağlantılarını göndermek ile görevlendirilmiş küçük bir botum. $1, beni JavaScript kullanarak yazdı.\n\n**Fandom ile resmi bir bağım yok, ben bir gayri resmi aracım!!**\n\nBeni Patreon'da da destekleyebilirsiniz:",
 	"helpserver": "Sorular ve sorunlarınız için lütfen destek sunucumu ziyaret edin:",
 	"patreon": "Bu, Patreon'a münhasır bir özellik!\nBu özelliğe erişim alabilmek için beni Patreon'da destekleyebilirsiniz:",
 	"settings": {
 		"save_failed": "ne yazık ki ayarlar kaydedilemedi, lütfen daha sonra tekrar deneyin.",
-		"missing": "bu sunucu henüz kurulmamış. %1$s ve %2$s'yi kullanarak ayarları değiştirin.",
+		"missing": "bu sunucu henüz kurulmamış. $1 ve $2'yi kullanarak ayarları değiştirin.",
 		"foundwikis": "Bu wikilerden herhangi birini mi kastediyorsun?",
 		"current": "sunucunun mevcut ayarları:",
 		"channel current": "kanalın mevcut ayarları:",
@@ -35,14 +37,14 @@
 		"langinvalid": "belirtilen dil şimdilik desteklenmiyor!",
 		"langchanged": "sunucunun dilini şuna değiştirdin:",
 		"channel langchanged": "kanalın dilini şuna değiştirdin:",
-		"langhelp": "`%s <dil>` kullanarak dili değiştirebilirsin.\nŞimdilik desteklenmeyen diller:",
+		"langhelp": "`$1 <dil>` kullanarak dili değiştirebilirsin.\nŞimdilik desteklenmeyen diller:",
 		"wikimissing": "bu sunucu için varsayılan bir wiki belirlenmemiş!",
 		"wiki": "bu sunucunun varsayılan wikisi:",
 		"channel wiki": "bu kanalın varsayılan wikisi:",
 		"wikiinvalid": "lütfen Gamepedia veya Fandom wikiye geçerli bir bağlantı saylağın!",
 		"wikichanged": "bu sunucunun varsayılan wikisini şuna değiştirdiniz:",
 		"channel wikichanged": "bu kanalın varsayılan wikisini şuna değiştirdiniz:",
-		"wikihelp": "`%s <wiki>` kullanarak varsayılan wikiyi değiştirebilirsin.\nWiki bağlantısı: `https://<wiki>.gamepedia.com/` veya `https://<wiki>.fandom.com/`",
+		"wikihelp": "`$1 <wiki>` kullanarak varsayılan wikiyi değiştirebilirsin.\nWiki bağlantısı: `https://<wiki>.gamepedia.com/` veya `https://<wiki>.fandom.com/`",
 		"prefix": "sunucudaki önekim:",
 		"prefixinvalid": "belirtilen önek desteklenmiyor!",
 		"prefixchanged": "sunucunun öneki şuna değiştirildi:"
@@ -53,14 +55,14 @@
 	},
 	"voice": {
 		"text": "Ses kanalındaki herkese belirli bir rol vermeye çalışıyorum:",
-		"enable": "Bu fonksiyonu etkinleştirmek için `%s` komutunu kullanmalısın.",
-		"disable": "Bu fonksiyou devre dışı bırakmak için `%s` komutunu kullanmalısın.",
+		"enable": "Bu fonksiyonu etkinleştirmek için `$1` komutunu kullanmalısın.",
+		"disable": "Bu fonksiyou devre dışı bırakmak için `$1` komutunu kullanmalısın.",
 		"enabled": "sesli kanallara rol ekleme fonksiyonunu etkinleştirdin.",
 		"disabled": "sesli kanallara rol ekleme fonksiyonunu devre dışı bıraktın.",
 		"name": "ses kanalı adı",
 		"channel": "Ses kanalı",
-		"join": "%1$s ses kanalına katıldı \"%2$s\".",
-		"left": "%1$s ses kanalından ayrıldı \"%2$s\"."
+		"join": "$1 ses kanalına katıldı \"$2\".",
+		"left": "$1 ses kanalından ayrıldı \"$2\"."
 	},
 	"overview": {
 		"inaccurate": "İstatistikler yanlış olabilir",
@@ -138,17 +140,17 @@
 			"loading": "Global istatistikler yükleniyor…"
 		},
 		"block": {
-			"header": "%s şu anda engelli!",
-			"text": "%1$s platformundan %3$s tarafından \"%4$s\" nedeniyle %2$s süresinde engellendiniz.",
-			"noreason": "%1$s platformundan %3$s tarafından %2$s tarihine kadar engellendiniz.",
-			"nofromtext": "\"%4$s\" nedeniyle %3$s tarafından %2$s tarihine kadar engellendiniz.",
-			"nofromnoreason": "%3$s tarafından %2$s tarihine kadar engellendiniz.",
+			"header": "$1 şu anda engelli!",
+			"text": "$1 platformundan $3 tarafından \"$4\" nedeniyle $2 süresinde engellendiniz.",
+			"noreason": "$1 platformundan $3 tarafından $2 tarihine kadar engellendiniz.",
+			"nofromtext": "\"$4\" nedeniyle $3 tarafından $2 tarihine kadar engellendiniz.",
+			"nofromnoreason": "$3 tarafından $2 tarihine kadar engellendiniz.",
 			"until_infinity": "bütün günlerin sonu"
 		},
 		"gblock": {
-			"header": "%s global olarak engellendi!",
-			"text": "%4$s platformunda %3$s tarafından %1$s tarihinde, %2$s tarihine kadar \"%5$s\" nedeniyle engellendi.",
-			"noreason": "%4$s platformunda %3$s tarafından %1$s tarihinde, %2$s tarihine kadar engellendi.",
+			"header": "$1 global olarak engellendi!",
+			"text": "$4 platformunda $3 tarafından $1 tarihinde, $2 tarihine kadar \"$5\" nedeniyle engellendi.",
+			"noreason": "$4 platformunda $3 tarafından $1 tarihinde, $2 tarihine kadar engellendi.",
 			"disabled": "Bu hesap şu anda devre dışı!"
 		}
 	},
@@ -160,7 +162,7 @@
 			"editor": "Editör:",
 			"timestamp": "Düzenleme tarihi:",
 			"size": "Fark:",
-			"bytes": "%s Bayt",
+			"bytes": "$1 {{PLURAL:$1|Bayt}}",
 			"comment": "açıklama:",
 			"tags": "Etiketler:",
 			"removed": "Kaldırıldı:",
@@ -172,36 +174,23 @@
 	"search": {
 		"page": "sayfa",
 		"search": "arama",
-		"infopage": "Doğru sonucu alamadın mı? Doğrudan bir bağlantı için %s kullan.",
-		"infosearch": "Doğru sonucu alamadın mı? Doğrudan bir bağlantı için %1$s, tüm isabetlerin listesi için %2$s kullan.",
+		"infopage": "Doğru sonucu alamadın mı? Doğrudan bir bağlantı için $1 kullan.",
+		"infosearch": "Doğru sonucu alamadın mı? Doğrudan bir bağlantı için $1, tüm isabetlerin listesi için $2 kullan.",
 		"category": {
 			"content": "Bu kategorinin içeriği:",
 			"empty": "*Bu kategori boş*",
-			"pages": {
-				"default": "%s sayfa"
-			},
-			"files": {
-				"default": "%s dosya"
-			},
-			"subcats": {
-				"default": "%s kategoriler",
-				"1": "%s kategori"
-			}
+			"pages": "$1 {{PLURAL:$1|sayfa}}",
+			"files": "$1 {{PLURAL:$1|dosya}}",
+			"subcats": "$1 {{PLURAL:$1|kategori|kategoriler}}"
 		},
 		"special": "Bu özel sayfanın içeriği:",
-		"empty": "*Bu özel sayfa boş*",
-		"results": {
-			"default": "%s total results",
-			"1": "%s total result"
-		}
+		"empty": "*Bu özel sayfa boş*"
 	},
 	"discussion": {
 		"post": "gönderisi",
 		"main": "Tartışmalar",
 		"image": "Resmi gör",
-		"votes": {
-			"default": "%s oy"
-		}
+		"votes": "$1 {{PLURAL:$1|oy}}"
 	},
 	"invite": {
 		"bot": "Beni başka bir sunucuya davet etmek için bu bağlantıyı kullan:"
@@ -288,7 +277,7 @@
 		"aliases": {
 			"cmd": "command"
 		},
-		"more": "Ve %s tane daha.",
-		"total": "%s hata düzeltildi"
+		"more": "Ve $1 {{PLURAL:$1|tane}} daha.",
+		"total": "$1 {{PLURAL:$1|hata}} düzeltildi"
 	}
 }

+ 52 - 61
i18n/zh.json

@@ -3,7 +3,9 @@
 		"机智的小鱼君",
 		"Dianliang233"
 	],
-	"lang": "zh",
+	"fallback": [
+		"en"
+	],
 	"dateformat": "zh-CN",
 	"aliases": {
 		"🎲": "random",
@@ -12,15 +14,15 @@
 		"验证": "verify",
 		"discussions": "discussion"
 	},
-	"prefix": "此伺服器的命令前缀是 `%s`。你可以使用 `%ssettings prefix` 更改前缀。关于全部命令,请见 `%shelp`。",
+	"prefix": "此伺服器的命令前缀是 `$1`。你可以使用 `$1settings prefix` 更改前缀。关于全部命令,请见 `$1help`。",
 	"missingperm": "为执行此命令我缺少一些权限:",
-	"limit": "🚨 **停一下,你太快了!** 🚨\n\n%s,你发出的指令过多了。",
-	"disclaimer": "我是一个链接到Gamepedia和Fandom wiki的小机器人。%s用JavaScript写了我。\n\n**我和Fandom无关联并为一个非官方工具!**\n\n你可以在Patreon支持我:",
+	"limit": "🚨 **停一下,你太快了!** 🚨\n\n$1,你发出的指令过多了。",
+	"disclaimer": "我是一个链接到Gamepedia和Fandom wiki的小机器人。$1用JavaScript写了我。\n\n**我和Fandom无关联并为一个非官方工具!**\n\n你可以在Patreon支持我:",
 	"helpserver": "有问题吗?你可以访问我的专属支援伺服器:",
 	"patreon": "这是Patreon赞助者专属功能!\n你可以在Patreon支持我来使用这个功能:",
 	"settings": {
 		"save_failed": "啊喔,设置保存失败了,请稍后再试。",
-		"missing": "这个伺服器尚未完成设置,使用 %1$s 以及 %2$s 来完成设置。",
+		"missing": "这个伺服器尚未完成设置,使用 $1 以及 $2 来完成设置。",
 		"foundwikis": "你也许指的是这些wiki?",
 		"current": "这是本伺服器的当前设置:",
 		"channel current": "这是本频道的当前设置:",
@@ -35,14 +37,14 @@
 		"langinvalid": "暂不支持这个语言!",
 		"langchanged": "您将本伺服器语言更改为:",
 		"channel langchanged": "您将本频道语言更改为:",
-		"langhelp": "使用 `%s <language>` 修改语言。\n目前支持的语言:",
+		"langhelp": "使用 `$1 <language>` 修改语言。\n目前支持的语言:",
 		"wikimissing": "暂未绑定wiki!",
 		"wiki": "本伺服器绑定的wiki是:",
 		"channel wiki": "本频道的wiki是:",
 		"wikiinvalid": "请提供到Gamepedia或Fandom wiki的有效链接!",
 		"wikichanged": "你已将伺服器wiki绑定为:",
 		"channel wikichanged": "你已将频道wiki修改为:",
-		"wikihelp": "使用 `%s <link>` 来更改默认wiki。\nWiki链接格式应为 `https://<wiki>.gamepedia.com/` 或 `https://<wiki>.fandom.com/`",
+		"wikihelp": "使用 `$1 <link>` 来更改默认wiki。\nWiki链接格式应为 `https://<wiki>.gamepedia.com/` 或 `https://<wiki>.fandom.com/`",
 		"prefix": "本伺服器的前缀是:",
 		"prefixinvalid": "不支持提供的前缀!",
 		"prefixchanged": "您将本伺服器命令前缀更改为:",
@@ -51,14 +53,14 @@
 			"channel inline": "行内命令目前已在本频道启用。",
 			"inlinechanged": "您启用了本伺服器的行内命令。",
 			"channel inlinechanged": "您启用了本频道的行内命令。",
-			"help": "使用 `%1$s` 禁用行内命令,例如 `[[%2$s]]` 和 `{{%2$s}}`。"
+			"help": "使用 `$1` 禁用行内命令,例如 `[[$2]]` 和 `{{$2}}`。"
 		},
 		"inline disabled": {
 			"inline": "行内命令目前已在本伺服器禁用。",
 			"channel inline": "行内命令目前已在本频道禁用。",
 			"inlinechanged": "您禁用了本伺服器的行内命令。",
 			"channel inlinechanged": "您禁用了本频道的行内命令。",
-			"help": "使用 `%1$s` 启用行内命令,例如 `[[%2$s]]` 和 `{{%2$s}}`。"
+			"help": "使用 `$1` 启用行内命令,例如 `[[$2]]` 和 `{{$2}}`。"
 		}
 	},
 	"pause": {
@@ -67,14 +69,14 @@
 	},
 	"voice": {
 		"text": "我将尝试给予每个在这个语音频道的用户一个特定的身份组:",
-		"enable": "使用 `%s` 启用本功能。",
-		"disable": "使用 `%s` 禁用本功能。",
+		"enable": "使用 `$1` 启用本功能。",
+		"disable": "使用 `$1` 禁用本功能。",
 		"enabled": "您启用了给予每个在这个语音频道的用户一个特定的身份组的功能。",
 		"disabled": "您禁用了给予每个在这个语音频道的用户一个特定的身份组的功能。",
 		"name": "语音频道名",
 		"channel": "语音频道",
-		"join": "%1$s 加入了语音频道“%2$s”。",
-		"left": "%1$s 离开了语音频道“%2$s”。"
+		"join": "$1 加入了语音频道“$2”。",
+		"left": "$1 离开了语音频道“$2”。"
 	},
 	"verification": {
 		"save_failed": "抱歉,无法保存验证方式,请稍后再试。",
@@ -84,7 +86,7 @@
 		"max_entries": "您已触发验证方式数量上限。",
 		"no_role": "请为新的验证方式提供身份组。",
 		"current": "这些是本伺服器目前的验证方式:",
-		"current_selected": "这些是本伺服器的验证方式 `%s`:",
+		"current_selected": "这些是本伺服器的验证方式 `$1`:",
 		"missing": "本伺服器尚未有验证方式。",
 		"add_more": "添加更多验证方式:",
 		"delete_current": "删除此验证方式:",
@@ -100,8 +102,8 @@
 		"enabled": "已启用",
 		"disabled": "已禁用",
 		"toggle": "(开关)",
-		"rename_no_permission": "**%s缺少 `管理昵称` 权限来强制更改为wiki用户名!**",
-		"role_too_high": "**%1$s 身份组的权重无法使 %2$s 添加!**",
+		"rename_no_permission": "**$1缺少 `管理昵称` 权限来强制更改为wiki用户名!**",
+		"role_too_high": "**$1 身份组的权重无法使 $2 添加!**",
 		"channel_max": "提供的频道过多。",
 		"channel_missing": "提供的频道不存在。",
 		"role_max": "提供的身份组过多。",
@@ -124,21 +126,21 @@
 		"failed_gblock": "**全域封禁检查失败!**",
 		"failed_roles": "**添加身份组失败!**",
 		"failed_rename": "**更改昵称失败!**",
-		"audit_reason": "已以“%s”的身份验证。",
-		"user_missing": "Wiki用户“%s”不存在。",
-		"user_missing_reply": "您连接的wiki用户“%s”不存在。",
-		"user_blocked": "**Wiki用户 %s 已被封禁!**",
-		"user_blocked_reply": "您连接的wiki用户**“%s”已被封禁!**",
-		"user_gblocked": "**Wiki用户 %s 已被全域封禁!**",
-		"user_gblocked_reply": "您连接的wiki用户**“%s”已被全域封禁!**",
-		"user_disabled": "**Wiki用户 %s 已被禁用!**",
-		"user_disabled_reply": "您连接的**“%s”的账号已被禁用!**",
-		"user_failed": "Discord用户%1$s无法和wiki用户 %2$s 对应。",
-		"user_failed_reply": "您的Discord用户名不和“%s”的对应。",
-		"user_matches": "Discord用户 %1$s 和wiki用户 %2$s 对应,但没有达到任何身份组的条件。",
-		"user_matches_reply": "您的Discord用户名和wiki用户“%s”对应,但您没有达到添加任何身份组的条件。",
-		"user_verified": "Discord用户 %1$s 已成功验证为wiki用户 %2$s。",
-		"user_verified_reply": "您已被成功验证为wiki用户“%s”",
+		"audit_reason": "已以“$1”的身份验证。",
+		"user_missing": "Wiki用户“$1”不存在。",
+		"user_missing_reply": "您连接的wiki用户“$1”不存在。",
+		"user_blocked": "**Wiki用户 $1 已被封禁!**",
+		"user_blocked_reply": "您连接的wiki用户**“$1”已被封禁!**",
+		"user_gblocked": "**Wiki用户 $1 已被全域封禁!**",
+		"user_gblocked_reply": "您连接的wiki用户**“$1”已被全域封禁!**",
+		"user_disabled": "**Wiki用户 $1 已被禁用!**",
+		"user_disabled_reply": "您连接的**“$1”的账号已被禁用!**",
+		"user_failed": "Discord用户$1无法和wiki用户 $2 对应。",
+		"user_failed_reply": "您的Discord用户名不和“$1”的对应。",
+		"user_matches": "Discord用户 $1 和wiki用户 $2 对应,但没有达到任何身份组的条件。",
+		"user_matches_reply": "您的Discord用户名和wiki用户“$1”对应,但您没有达到添加任何身份组的条件。",
+		"user_verified": "Discord用户 $1 已成功验证为wiki用户 $2。",
+		"user_verified_reply": "您已被成功验证为wiki用户“$1”",
 		"user_renamed": "用户的Discord昵称已被更改为其wiki用户名",
 		"discord": "Discord用户:",
 		"wiki": "Wiki用户:",
@@ -146,10 +148,10 @@
 		"notice": "注意:",
 		"qualified": "符合:",
 		"qualified_error": "符合,但无法添加:",
-		"help_guide": "请根据[此指导](%s)来添加您的Discord用户名至您的wiki资料页。",
-		"help_gamepedia": "https://help.gamepedia.com/Gamepedia_Help_Wiki:Discord_verification",
+		"help_guide": "请根据[此指导]($1)来添加您的Discord用户名至您的wiki资料页。",
+		"help_gamepedia": "https://help-zh.gamepedia.com/Gamepedia帮助Wiki:Discord验证",
 		"help_fandom": "https://community.fandom.com/zh/wiki/Special:VerifyUser",
-		"help_subpage": "请将您的Discord用户名(%s)添加至您在wiki上的Discord子页面:"
+		"help_subpage": "请将您的Discord用户名($1)添加至您在wiki上的Discord子页面:"
 	},
 	"overview": {
 		"inaccurate": "统计信息可能出错",
@@ -227,17 +229,17 @@
 			"loading": "正在加载全域数据…"
 		},
 		"block": {
-			"header": "%s 目前正被封禁!",
-			"text": "被 %3$s 封禁,时间从 %1$s 到 %2$s,理由:“%4$s”",
-			"noreason": "被 %3$s 封禁,时间从 %1$s 到 %2$s",
-			"nofromtext": "被 %3$s 封禁,直到 %2$s,理由:“%4$s”",
-			"nofromnoreason": "被 %3$s 封禁,直到 %2$s",
+			"header": "$1 目前正被封禁!",
+			"text": "被 $3 封禁,时间从 $1 到 $2,理由:“$4”",
+			"noreason": "被 $3 封禁,时间从 $1 到 $2",
+			"nofromtext": "被 $3 封禁,直到 $2,理由:“$4”",
+			"nofromnoreason": "被 $3 封禁,直到 $2",
 			"until_infinity": "天荒地老"
 		},
 		"gblock": {
-			"header": "%s 目前正被全域封禁!",
-			"text": "被 %3$s 封禁,时间从 %1$s 到 %2$s,理由:“%4$s”",
-			"noreason": "被 %3$s 封禁,时间从 %1$s 到 %2$s",
+			"header": "$1 目前正被全域封禁!",
+			"text": "被 $3 封禁,时间从 $1 到 $2,理由:“$4”",
+			"noreason": "被 $3 封禁,时间从 $1 到 $2",
 			"disabled": "此账户目前正被禁用!"
 		}
 	},
@@ -249,7 +251,7 @@
 			"editor": "编辑者:",
 			"timestamp": "时间:",
 			"size": "差异:",
-			"bytes": "%s Bytes",
+			"bytes": "$1 {{PLURAL:$1|Byte|Bytes}}",
 			"comment": "摘要:",
 			"tags": "标签:",
 			"removed": "删除:",
@@ -261,35 +263,24 @@
 	"search": {
 		"page": "页面",
 		"search": "搜索",
-		"infopage": "结果不正确?用%s来指定连接",
-		"infosearch": "结果不正确?用%1$s来指定链接或%2$s列出所有结果",
+		"infopage": "结果不正确?用$1来指定连接",
+		"infosearch": "结果不正确?用$1来指定链接或$2列出所有结果",
 		"category": {
 			"content": "分类下的内容:",
 			"empty": "*空白分类*",
-			"pages": {
-				"default": "%s 个页面"
-			},
-			"files": {
-				"default": "%s 个文件"
-			},
-			"subcats": {
-				"default": "%s 个分类"
-			}
+			"pages": "$1 {{PLURAL:$1|个页面}}",
+			"files": "$1 {{PLURAL:$1|个文件}}",
+			"subcats": "$1 {{PLURAL:$1|个分类}}"
 		},
 		"special": "此特殊页面的内容:",
 		"empty": "*此特殊页面内容为空*",
-		"results": {
-			"default": "共 %s 个结果",
-			"1": "共 %s 个结果"
-		}
+		"results": "共 $1 {{PLURAL:$1|个结果}}"
 	},
 	"discussion": {
 		"post": "帖子",
 		"main": "讨论版",
 		"image": "查看图片",
-		"votes": {
-			"default": "%s 个投票"
-		}
+		"votes": "$1 {{PLURAL:$1|个投票}}"
 	},
 	"invite": {
 		"bot": "使用这个链接把我添加到其他伺服器:"

+ 14 - 1
util/allSites.js

@@ -21,7 +21,20 @@ function getAllSites() {
 	} );
 }
 
+function updateAllSites() {
+	return new Promise( function(resolve, reject) {
+		getAllSites.then( newSites => {
+			if ( newSites.length ) allSites.then( sites => {
+				sites.splice(0, sites.length);
+				sites.push(...newSites);
+				resolve(sites);
+			} );
+			else resolve(newSites);
+		} );
+	} );
+}
+
 module.exports = {
-	get: getAllSites,
+	update: updateAllSites,
 	then: (callback) => allSites.then(callback)
 };

+ 68 - 0
util/default.json

@@ -63,5 +63,73 @@
 		"authenticated",
 		"autoconfirmed",
 		"user"
+	],
+	"wikiProjects": [
+		{
+			"name": "wikipedia.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikipedia\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "mediawiki.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?mediawiki\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikimedia.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikimedia\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wiktionary.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wiktionary\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikibooks.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikibooks\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikisource.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikisource\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikidata.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikidata\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikiversity.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikiversity\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikiquote.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikiquote\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikinews.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikinews\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		},
+		{
+			"name": "wikivoyage.org",
+			"regex": "((?:[a-z\\d-]{1,50}\\.)?wikivoyage\\.org)(?:\\/wiki\\/|\\/?$)",
+			"articlePath": "/wiki/",
+			"scriptPath": "/w/"
+		}
 	]
 }

+ 63 - 8
util/i18n.js

@@ -6,27 +6,38 @@ class Lang {
 	constructor(lang = defaultSettings.lang, namespace = '') {
 		this.lang = lang;
 		this.namespace = namespace;
+		this.fallback = ( i18n?.[lang]?.fallback || [] );
 	}
 
-	get(message = '') {
+	get(message = '', ...args) {
 		if ( this.namespace.length ) message = this.namespace + '.' + message;
-		let args = ( message.length ? message.split('.') : [] );
+		let keys = ( message.length ? message.split('.') : [] );
 		let lang = this.lang;
 		let text = i18n?.[lang];
-		for (let n = 0; n < args.length; n++) {
+		let fallback = 0;
+		for (let n = 0; n < keys.length; n++) {
 			if ( text ) {
-				text = text?.[args[n]];
+				text = text?.[keys[n]];
 			}
-			else if ( lang !== defaultSettings.lang ) {
-				lang = defaultSettings.lang;
+			else if ( fallback < this.fallback.length ) {
+				lang = this.fallback[fallback];
+				fallback++;
 				text = i18n?.[lang];
 				n = 0;
 			}
 			else {
-				n = args.length;
+				n = keys.length;
 			}
 		}
-		return ( text || '⧼' + message + '⧽' );
+		if ( typeof text === 'string' ) {
+			args.forEach( (arg, i) => {
+				text = text.replaceSave( new RegExp( `\\$${i + 1}`, 'g' ), arg );
+			} );
+			text = text.replace( /{{\s*PLURAL:\s*(\d+)\s*\|\s*([^\{\}]*?)\s*}}/g, (m, number, cases) => {
+				return plural(lang, parseInt(number, 10), cases.split(/\s*\|\s*/));
+			} );
+		}
+		return ( text || '⧼' + message + ( isDebug && args.length ? ': ' + args.join(', ') : '' ) + '⧽' );
 	}
 
 	static allLangs() {
@@ -34,4 +45,48 @@ class Lang {
 	}
 }
 
+function plural(lang, number, args) {
+	var text = args[args.length - 1];
+	switch ( lang ) {
+		case 'fr':
+			if ( number <= 1 ) text = getArg(args, 0);
+			else text = getArg(args, 1);
+			break;
+		case 'pl':
+			if ( number === 1 ) text = getArg(args, 0);
+			else if ( [2,3,4].includes( number % 10 ) && ![12,13,14].includes( number % 100 ) ) {
+				text = getArg(args, 1);
+			}
+			else text = getArg(args, 2);
+			break;
+		case 'ru':
+			if ( args.length > 2 ) {
+				if ( number % 10 === 1 && number % 100 !== 11 ) text = getArg(args, 0);
+				else if ( [2,3,4].includes( number % 10 ) && ![12,13,14].includes( number % 100 ) ) {
+					text = getArg(args, 1);
+				}
+				else text = getArg(args, 2);
+			}
+			else {
+				if ( number === 1 ) text = getArg(args, 0);
+				else text = getArg(args, 1);
+			}
+			break;
+		case 'de':
+		case 'en':
+		case 'nl':
+		case 'pt':
+		case 'tr':
+		case 'zh':
+		default:
+			if ( number === 1 ) text = getArg(args, 0);
+			else text = getArg(args, 1);
+	}
+	return text;
+}
+
+function getArg(args, index) {
+	return ( args.length > index ? args[index] : args[args.length - 1] );
+}
+
 module.exports = Lang;

+ 3 - 3
util/newMessage.js

@@ -43,7 +43,7 @@ function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env
 		if ( count === maxcount ) {
 			console.log( '- Message contains too many commands!' );
 			msg.reactEmoji('⚠️');
-			msg.sendChannelError( lang.get('limit').replaceSave( '%s', '<@' + author.id + '>' ), {allowedMentions:{users:[author.id]}} );
+			msg.sendChannelError( lang.get('limit', '<@' + author.id + '>'), {allowedMentions:{users:[author.id]}} );
 			return;
 		}
 		line = line.substring(prefix.length);
@@ -59,13 +59,13 @@ function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env
 			else if ( /^![a-z\d-]{1,50}$/.test(invoke) ) {
 				cmdmap.LINK(lang, msg, args.join(' '), 'https://' + invoke.substring(1) + '.gamepedia.com/', invoke + ' ');
 			}
-			else if ( /^\?(?:[a-z-]{1,8}\.)?[a-z\d-]{1,50}$/.test(invoke) ) {
+			else if ( /^\?(?:[a-z-]{2,12}\.)?[a-z\d-]{1,50}$/.test(invoke) ) {
 				var invokeWiki = wiki;
 				if ( invoke.includes( '.' ) ) invokeWiki = 'https://' + invoke.split('.')[1] + '.fandom.com/' + invoke.substring(1).split('.')[0] + '/';
 				else invokeWiki = 'https://' + invoke.substring(1) + '.fandom.com/';
 				cmdmap.LINK(lang, msg, args.join(' '), invokeWiki, invoke + ' ');
 			}
-			else if ( /^\?\?(?:[a-z-]{1,8}\.)?[a-z\d-]{1,50}$/.test(invoke) ) {
+			else if ( /^\?\?(?:[a-z-]{2,12}\.)?[a-z\d-]{1,50}$/.test(invoke) ) {
 				var invokeWiki = wiki;
 				if ( invoke.includes( '.' ) ) invokeWiki = 'https://' + invoke.split('.')[1] + '.wikia.org/' + invoke.substring(2).split('.')[0] + '/';
 				else invokeWiki = 'https://' + invoke.substring(2) + '.wikia.org/';