diff --git a/.gitignore b/.gitignore
index ddb37093ba..144cfe268a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,7 @@ pidfile
 
 ## Directory-based project format:
 .idea/
+.vscode/
 
 ## File-based project format:
 *.ipr
diff --git a/.tx/config b/.tx/config
index a7f7e2f98b..65d891131e 100644
--- a/.tx/config
+++ b/.tx/config
@@ -919,4 +919,50 @@ trans.tr = public/language/tr/groups.json
 trans.vi = public/language/vi/groups.json
 trans.zh_CN = public/language/zh_CN/groups.json
 trans.zh_TW = public/language/zh_TW/groups.json
+type = KEYVALUEJSON
+
+[nodebb.uploads]
+file_filter = public/language/<lang>/uploads.json
+source_file = public/language/en_GB/uploads.json
+source_lang = en_GB
+trans.ar = public/language/ar/uploads.json
+trans.bn = public/language/bn/uploads.json
+trans.bg = public/language/bg/uploads.json
+trans.cs = public/language/cs/uploads.json
+trans.da = public/language/da/uploads.json
+trans.de = public/language/de/uploads.json
+trans.el = public/language/el/uploads.json
+trans.en_US = public/language/en_US/uploads.json
+trans.en@pirate = public/language/en@pirate/uploads.json
+trans.es = public/language/es/uploads.json
+trans.et = public/language/et/uploads.json
+trans.fa_IR = public/language/fa_IR/uploads.json
+trans.fi = public/language/fi/uploads.json
+trans.fr = public/language/fr/uploads.json
+trans.gl = public/language/gl/uploads.json
+trans.he = public/language/he/uploads.json
+trans.hu = public/language/hu/uploads.json
+trans.id = public/language/id/uploads.json
+trans.it = public/language/it/uploads.json
+trans.ja = public/language/ja/uploads.json
+trans.ko = public/language/ko/uploads.json
+trans.lt = public/language/lt/uploads.json
+trans.ms = public/language/ms/uploads.json
+trans.nb = public/language/nb/uploads.json
+trans.nl = public/language/nl/uploads.json
+trans.pl = public/language/pl/uploads.json
+trans.pt_BR = public/language/pt_BR/uploads.json
+trans.ru = public/language/ru/uploads.json
+trans.ro = public/language/ro/uploads.json
+trans.rw = public/language/rw/uploads.json
+trans.sc = public/language/sc/uploads.json
+trans.sk = public/language/sk/uploads.json
+trans.sl = public/language/sl/uploads.json
+trans.sr = public/language/sr/uploads.json
+trans.sv = public/language/sv/uploads.json
+trans.th = public/language/th/uploads.json
+trans.tr = public/language/tr/uploads.json
+trans.vi = public/language/vi/uploads.json
+trans.zh_CN = public/language/zh_CN/uploads.json
+trans.zh_TW = public/language/zh_TW/uploads.json
 type = KEYVALUEJSON
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
index 0621f8c1a8..a6aca2083a 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -17,17 +17,20 @@ module.exports = function(grunt) {
 			args.push('--log-level=info');
 		}
 		
-		if (target === 'lessUpdated') {
-			fromFile = ['js','tpl'];
-			compiling = 'less';
+		if (target === 'lessUpdated_Client') {
+			fromFile = ['js', 'tpl', 'acpLess'];
+			compiling = 'clientLess';
+		} else if (target === 'lessUpdated_Admin') {
+			fromFile = ['js', 'tpl', 'clientLess'];
+			compiling = 'acpLess';
 		} else if (target === 'clientUpdated') {
-			fromFile = ['less','tpl'];
+			fromFile = ['clientLess', 'acpLess', 'tpl'];
 			compiling = 'js';
 		} else if (target === 'templatesUpdated') {
-			fromFile = ['js','less'];
+			fromFile = ['js', 'clientLess', 'acpLess'];
 			compiling = 'tpl';
 		} else if (target === 'serverUpdated') {
-			fromFile = ['less','js','tpl'];
+			fromFile = ['clientLess', 'acpLess', 'js', 'tpl'];
 		}
 
 		fromFile = fromFile.filter(function(ext) {
@@ -53,17 +56,36 @@ module.exports = function(grunt) {
 
 	grunt.initConfig({
 		watch: {
-			lessUpdated: {
-				files: ['public/**/*.less', 'node_modules/nodebb-*/*.less', 'node_modules/nodebb-*/*/*.less', 'node_modules/nodebb-*/*/*/*.less', 'node_modules/nodebb-*/*/*/*/*.less']
+			lessUpdated_Client: {
+				files: [
+					'public/*.less',
+					'node_modules/nodebb-*/*.less', 'node_modules/nodebb-*/**/*.less',
+					'!node_modules/nodebb-*/node_modules/**',
+					'!node_modules/nodebb-*/.git/**'
+				]
+			},
+			lessUpdated_Admin: {
+				files: ['public/**/*.less']
 			},
 			clientUpdated: {
-				files: ['public/src/**/*.js', '!public/src/modules/*.js', 'node_modules/nodebb-*/*.js', 'node_modules/nodebb-*/*/*.js', 'node_modules/nodebb-*/*/*/*.js', 'node_modules/nodebb-*/*/*/*/*.js', 'node_modules/templates.js/lib/templates.js']
+				files: [
+					'public/src/**/*.js',
+					'node_modules/nodebb-*/*.js', 'node_modules/nodebb-*/**/*.js',
+					'!node_modules/nodebb-*/node_modules/**',
+					'node_modules/templates.js/lib/templates.js',
+					'!node_modules/nodebb-*/.git/**'
+				]
 			},
 			serverUpdated: {
 				files: ['*.js', 'install/*.js', 'src/**/*.js']
 			},
 			templatesUpdated: {
-				files: ['src/views/**/*.tpl', 'node_modules/nodebb-*/*.tpl', 'node_modules/nodebb-*/*/*.tpl', 'node_modules/nodebb-*/*/*/*.tpl', 'node_modules/nodebb-*/*/*/*/*.tpl', 'node_modules/nodebb-*/*/*/*/*/*.tpl']
+				files: [
+					'src/views/**/*.tpl',
+					'node_modules/nodebb-*/*.tpl', 'node_modules/nodebb-*/**/*.tpl',
+					'!node_modules/nodebb-*/node_modules/**',
+					'!node_modules/nodebb-*/.git/**'
+				]
 			}
 		}
 	});
diff --git a/app.js b/app.js
index 445b21c845..ed5238285e 100644
--- a/app.js
+++ b/app.js
@@ -25,13 +25,11 @@ nconf.argv().env('__');
 
 var url = require('url'),
 	async = require('async'),
-	semver = require('semver'),
 	winston = require('winston'),
 	colors = require('colors'),
 	path = require('path'),
 	pkg = require('./package.json'),
-	file = require('./src/file'),
-	utils = require('./public/src/utils.js');
+	file = require('./src/file');
 
 global.env = process.env.NODE_ENV || 'production';
 
@@ -151,8 +149,7 @@ function start() {
 				meta.reload();
 			break;
 			case 'js-propagate':
-				meta.js.cache = message.cache;
-				meta.js.map = message.map;
+				meta.js.target = message.data;
 				emitter.emit('meta:js.compiled');
 				winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid);
 			break;
diff --git a/install/data/navigation.json b/install/data/navigation.json
index 5956c6b05f..0ec72805de 100644
--- a/install/data/navigation.json
+++ b/install/data/navigation.json
@@ -79,9 +79,7 @@
 		"textClass": "visible-xs-inline",
 		"text": "\\[\\[global:header.search\\]\\]",
 		"properties": {
-			"installed": {
-				"search": true
-			}
+			"searchInstalled": true
 		}
 	}
 ]
\ No newline at end of file
diff --git a/loader.js b/loader.js
index 20e8335a9a..b78abdc4a1 100644
--- a/loader.js
+++ b/loader.js
@@ -24,8 +24,7 @@ var	pidFilePath = __dirname + '/pidfile',
 	Loader = {
 		timesStarted: 0,
 		js: {
-			cache: undefined,
-			map: undefined
+			target: {}
 		},
 		css: {
 			cache: undefined,
@@ -35,8 +34,9 @@ var	pidFilePath = __dirname + '/pidfile',
 
 Loader.init = function(callback) {
 	if (silent) {
-		console.log = function(value) {
-			output.write(value + '\n');
+		console.log = function() {
+			var args = Array.prototype.slice.call(arguments);
+			output.write(args.join(' ') + '\n');
 		};
 	}
 
@@ -86,11 +86,21 @@ Loader.addWorkerEvents = function(worker) {
 		if (message && typeof message === 'object' && message.action) {
 			switch (message.action) {
 				case 'ready':
-					if (Loader.js.cache && !worker.isPrimary) {
+					if (Loader.js.target['nodebb.min.js'] && Loader.js.target['nodebb.min.js'].cache && !worker.isPrimary) {
 						worker.send({
 							action: 'js-propagate',
-							cache: Loader.js.cache,
-							map: Loader.js.map
+							cache: Loader.js.target['nodebb.min.js'].cache,
+							map: Loader.js.target['nodebb.min.js'].map,
+							target: 'nodebb.min.js'
+						});
+					}
+
+					if (Loader.js.target['acp.min.js'] && Loader.js.target['acp.min.js'].cache && !worker.isPrimary) {
+						worker.send({
+							action: 'js-propagate',
+							cache: Loader.js.target['acp.min.js'].cache,
+							map: Loader.js.target['acp.min.js'].map,
+							target: 'acp.min.js'
 						});
 					}
 
@@ -113,13 +123,11 @@ Loader.addWorkerEvents = function(worker) {
 					Loader.reload();
 				break;
 				case 'js-propagate':
-					Loader.js.cache = message.cache;
-					Loader.js.map = message.map;
+					Loader.js.target = message.data;
 
 					Loader.notifyWorkers({
 						action: 'js-propagate',
-						cache: message.cache,
-						map: message.map
+						data: message.data
 					}, worker.pid);
 				break;
 				case 'css-propagate':
diff --git a/nodebb b/nodebb
index 233dbd350f..417c6a9d80 100755
--- a/nodebb
+++ b/nodebb
@@ -4,6 +4,10 @@ var colors = require('colors'),
 	cproc = require('child_process'),
 	argv = require('minimist')(process.argv.slice(2)),
 	fs = require('fs'),
+	path = require('path'),
+	request = require('request'),
+	semver = require('semver'),
+	prompt = require('prompt'),
 	async = require('async');
 
 var getRunningPid = function(callback) {
@@ -21,14 +25,197 @@ var getRunningPid = function(callback) {
 				callback(e);
 			}
 		});
-	};
+	},
+	getCurrentVersion = function(callback) {
+		fs.readFile(path.join(__dirname, 'package.json'), { encoding: 'utf-8' }, function(err, pkg) {
+			try {
+				pkg = JSON.parse(pkg);
+				return callback(null, pkg.version);
+			} catch(err) {
+				return callback(err);
+			}
+		})
+	},
+	fork = function (args) {
+		cproc.fork('app.js', args, {
+			cwd: __dirname,
+			silent: false
+		});
+	},
+	getInstalledPlugins = function(callback) {
+		async.parallel({
+			files: async.apply(fs.readdir, path.join(__dirname, 'node_modules')),
+			deps: async.apply(fs.readFile, path.join(__dirname, 'package.json'), { encoding: 'utf-8' })
+		}, function(err, payload) {
+			var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w\-]+$/,
+				moduleName, isGitRepo;
 
-function fork(args) {
-	cproc.fork('app.js', args, {
-		cwd: __dirname,
-		silent: false
-	});
-}
+			payload.files = payload.files.filter(function(file) {
+				return isNbbModule.test(file);
+			});
+
+			try {
+				payload.deps = JSON.parse(payload.deps).dependencies;
+				payload.bundled = [];
+				payload.installed = [];
+			} catch (err) {
+				return callback(err);
+			}
+
+			for (moduleName in payload.deps) {
+				if (isNbbModule.test(moduleName)) {
+					payload.bundled.push(moduleName);
+				}
+			}
+
+			// Whittle down deps to send back only extraneously installed plugins/themes/etc
+			payload.files.forEach(function(moduleName) {
+				try {
+					fs.accessSync(path.join(__dirname, 'node_modules/' + moduleName, '.git'));
+					isGitRepo = true;
+				} catch(e) {
+					isGitRepo = false;
+				}
+
+				if (
+					payload.files.indexOf(moduleName) !== -1	// found in `node_modules/`
+					&& payload.bundled.indexOf(moduleName) === -1	// not found in `package.json`
+					&& !fs.lstatSync(path.join(__dirname, 'node_modules/' + moduleName)).isSymbolicLink()	// is not a symlink
+					&& !isGitRepo	// .git/ does not exist, so it is not a git repository
+				) {
+					payload.installed.push(moduleName);
+				}
+			});
+
+			getModuleVersions(payload.installed, callback);
+		});
+	},
+	getModuleVersions = function(modules, callback) {
+		var versionHash = {};
+
+		async.eachLimit(modules, 50, function(module, next) {
+			fs.readFile(path.join(__dirname, 'node_modules/' + module + '/package.json'), { encoding: 'utf-8' }, function(err, pkg) {
+				try {
+					pkg = JSON.parse(pkg);
+					versionHash[module] = pkg.version;
+					next();
+				} catch (err) {
+					next(err);
+				}
+			});
+		}, function(err) {
+			callback(err, versionHash);
+		});
+	},
+	checkPlugins = function(standalone, callback) {
+		if (standalone) {
+			process.stdout.write('Checking installed plugins and themes for updates... ');
+		}
+
+		async.waterfall([
+			async.apply(async.parallel, {
+				plugins: async.apply(getInstalledPlugins),
+				version: async.apply(getCurrentVersion)
+			}),
+			function(payload, next) {
+				if (!payload.plugins.length) {
+					process.stdout.write('OK'.green + '\n'.reset);
+					return next(null, []);	// no extraneous plugins installed
+				}
+
+				var toCheck = Object.keys(payload.plugins);
+				request({
+					method: 'GET',
+					url: 'https://packages.nodebb.org/api/v1/suggest?version=' + payload.version + '&package[]=' + toCheck.join('&package[]='),
+					json: true
+				}, function(err, res, body) {
+					if (err) {
+						process.stdout.write('error'.red + '\n'.reset);
+						return next(err);
+					}
+					process.stdout.write('OK'.green + '\n'.reset);
+
+					if (!Array.isArray(body) && toCheck.length === 1) {
+						body = [body];
+					}
+
+					var current, suggested,
+						upgradable = body.map(function(suggestObj) {
+							current = payload.plugins[suggestObj.package];
+							suggested = suggestObj.version;
+
+							if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) {
+								return {
+									name: suggestObj.package,
+									current: current,
+									suggested: suggested
+								}
+							} else {
+								return null;
+							}
+						}).filter(Boolean);
+
+					next(null, upgradable);
+				})
+			}
+		], callback);
+	},
+	upgradePlugins = function(callback) {
+		var standalone = false;
+		if (typeof callback !== 'function') {
+			callback = function() {};
+			standalone = true;
+		};
+
+		checkPlugins(standalone, function(err, found) {
+			if (err) {
+				process.stdout.write('\Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset);
+				return callback(err);
+			}
+
+			if (found && found.length) {
+				process.stdout.write('\nA total of ' + new String(found.length).bold + ' package(s) can be upgraded:\n');
+				found.forEach(function(suggestObj) {
+					process.stdout.write('  * '.yellow + suggestObj.name.reset + ' (' + suggestObj.current.yellow + ' -> '.reset + suggestObj.suggested.green + ')\n'.reset);
+				});
+				process.stdout.write('\n');
+			} else {
+				if (standalone) {
+					process.stdout.write('\nAll packages up-to-date!'.green + '\n'.reset);
+				}
+				return callback();
+			}
+
+			prompt.message = '';
+			prompt.delimiter = '';
+
+			prompt.start();
+			prompt.get({
+				name: 'upgrade',
+				description: 'Proceed with upgrade (y|n)?'.reset,
+				type: 'string'
+			}, function(err, result) {
+				if (result.upgrade === 'y' || result.upgrade === 'yes') {
+					process.stdout.write('\nUpgrading packages...');
+					var args = ['npm', 'i'];
+					found.forEach(function(suggestObj) {
+						args.push(suggestObj.name + '@' + suggestObj.suggested);
+					});
+
+					require('child_process').execFile('/usr/bin/env', args, { stdio: 'ignore' }, function(err) {
+						if (!err) {
+							process.stdout.write(' OK\n'.green);
+						}
+
+						callback(err);
+					});
+				} else {
+					process.stdout.write('\nPackage upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade-plugins'.green + '".\n'.reset);
+					callback();
+				}
+			})
+		});
+	};
 
 switch(process.argv[2]) {
 	case 'status':
@@ -130,15 +317,23 @@ switch(process.argv[2]) {
 		fork(args);
 		break;
 
+	case 'upgrade-plugins':
+		upgradePlugins();
+		break;
+
 	case 'upgrade':
 		async.series([
 			function(next) {
 				process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... '.yellow);
-				require('child_process').execFile('/usr/bin/env', ['npm', 'i', '--production'], next);
+				require('child_process').execFile('/usr/bin/env', ['npm', 'i', '--production'], { stdio: 'ignore' }, next);
 			},
 			function(next) {
 				process.stdout.write('OK\n'.green);
-				process.stdout.write('2. '.bold + 'Updating NodeBB data store schema.\n'.yellow);
+				process.stdout.write('2. '.bold + 'Checking installed plugins for updates... '.yellow);
+				upgradePlugins(next);
+			},
+			function(next) {
+				process.stdout.write('3. '.bold + 'Updating NodeBB data store schema...\n'.yellow);
 				var upgradeProc = cproc.fork('app.js', ['--upgrade'], {
 					cwd: __dirname,
 					silent: false
diff --git a/package.json b/package.json
index 70758c5ce3..71a8630853 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "nodebb",
   "license": "GPL-3.0",
   "description": "NodeBB Forum",
-  "version": "0.9.4",
+  "version": "1.0.2",
   "homepage": "http://www.nodebb.org",
   "repository": {
     "type": "git",
@@ -34,6 +34,7 @@
     "express-session": "^1.8.2",
     "express-useragent": "0.2.4",
     "html-to-text": "2.0.0",
+    "ip": "1.1.2",
     "jimp": "0.2.21",
     "less": "^2.0.0",
     "logrotate-stream": "^0.2.3",
@@ -45,18 +46,18 @@
     "morgan": "^1.3.2",
     "mousetrap": "^1.5.3",
     "nconf": "~0.8.2",
-    "nodebb-plugin-composer-default": "3.0.6",
-    "nodebb-plugin-dbsearch": "0.3.1",
-    "nodebb-plugin-emoji-extended": "0.5.0",
-    "nodebb-plugin-markdown": "4.0.17",
-    "nodebb-plugin-mentions": "1.0.17",
-    "nodebb-plugin-soundpack-default": "0.1.5",
-    "nodebb-plugin-spam-be-gone": "0.4.5",
-    "nodebb-rewards-essentials": "0.0.6",
-    "nodebb-theme-lavender": "3.0.8",
-    "nodebb-theme-persona": "4.0.87",
-    "nodebb-theme-vanilla": "5.0.52",
-    "nodebb-widget-essentials": "2.0.6",
+    "nodebb-plugin-composer-default": "3.0.18",
+    "nodebb-plugin-dbsearch": "1.0.1",
+    "nodebb-plugin-emoji-extended": "1.0.3",
+    "nodebb-plugin-markdown": "5.0.1",
+    "nodebb-plugin-mentions": "1.0.21",
+    "nodebb-plugin-soundpack-default": "0.1.6",
+    "nodebb-plugin-spam-be-gone": "0.4.6",
+    "nodebb-rewards-essentials": "0.0.8",
+    "nodebb-theme-lavender": "3.0.9",
+    "nodebb-theme-persona": "4.0.115",
+    "nodebb-theme-vanilla": "5.0.61",
+    "nodebb-widget-essentials": "2.0.9",
     "nodemailer": "2.0.0",
     "nodemailer-sendmail-transport": "1.0.0",
     "nodemailer-smtp-transport": "^2.4.1",
@@ -76,10 +77,10 @@
     "socket.io-redis": "^1.0.0",
     "socketio-wildcard": "~0.3.0",
     "string": "^3.0.0",
-    "templates.js": "0.3.1",
+    "templates.js": "0.3.4",
     "toobusy-js": "^0.4.2",
     "uglify-js": "^2.6.0",
-    "underscore": "~1.8.3",
+    "underscore": "^1.8.3",
     "underscore.deep": "^0.5.1",
     "validator": "^5.0.0",
     "winston": "^2.1.0",
@@ -88,7 +89,7 @@
   "devDependencies": {
     "mocha": "~1.13.0",
     "grunt": "~0.4.5",
-    "grunt-contrib-watch": "^0.6.1"
+    "grunt-contrib-watch": "^1.0.0"
   },
   "bugs": {
     "url": "https://github.com/NodeBB/NodeBB/issues"
diff --git a/public/js-enabled.css b/public/js-enabled.css
new file mode 100644
index 0000000000..f59881974e
--- /dev/null
+++ b/public/js-enabled.css
@@ -0,0 +1,7 @@
+/*
+    The following stylesheet is only included on pages that can execute javascript
+*/
+
+[component="post/content"] img:not(.not-responsive):not([data-state]) {
+	display: none !important;
+}
\ No newline at end of file
diff --git a/public/language/ar/error.json b/public/language/ar/error.json
index d4c19bc98a..635f404c8c 100644
--- a/public/language/ar/error.json
+++ b/public/language/ar/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "المستخدم محظور",
     "user-too-new": "عذرا, يجب أن تنتظر 1% ثواني قبل قيامك بأول مشاركة",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "قائمة غير موجودة",
     "no-topic": "موضوع غير موجود",
     "no-post": "رد غير موجود",
@@ -50,8 +51,8 @@
     "still-uploading": "الرجاء انتظار الرفع",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "لقد سبق وأضفت هذا الرد إلى المفضلة",
-    "already-unfavourited": "لقد سبق وحذفت هذا الرد من المفضلة",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "لايمكن حظر مدبر نظام آخر.",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "الرجاء استعمال اسم المستخدم الخاص بك للدخول",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/ar/global.json b/public/language/ar/global.json
index 6a40d450e3..e0a2d51643 100644
--- a/public/language/ar/global.json
+++ b/public/language/ar/global.json
@@ -86,5 +86,9 @@
     "delete_all": "حذف الكل",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/ar/groups.json b/public/language/ar/groups.json
index a76ac459b4..673fba23e2 100644
--- a/public/language/ar/groups.json
+++ b/public/language/ar/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "مخفي",
     "details.hidden_help": "في حالة تفعيل الخيار، لن تظهر المجموعة للعموم والإنضمام إليها سيتلزم دعوة.",
     "details.delete_group": "حذف المجموعة",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "تم تحديث بيانات المجموعة",
     "event.deleted": "تم حذف المجموعة %1",
     "membership.accept-invitation": "اقبل الدعوة",
@@ -48,5 +49,6 @@
     "membership.join-group": "انظم للمجموعة",
     "membership.leave-group": "غادر المجموعة",
     "membership.reject": "رفض",
-    "new-group.group_name": "اسم المجموعة"
+    "new-group.group_name": "اسم المجموعة",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json
index 1c55a5c1db..1c23da87ad 100644
--- a/public/language/ar/modules.json
+++ b/public/language/ar/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 يكتب رسالة...",
     "chat.user_has_messaged_you": "%1 أرسل لك رسالة.",
     "chat.see_all": "عرض كل المحادثات",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "المرجو اختيار مرسل إليه لمعاينة تاريخ الدردشات",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "آخر الدردشات",
diff --git a/public/language/ar/notifications.json b/public/language/ar/notifications.json
index 83c54e683b..81733db21e 100644
--- a/public/language/ar/notifications.json
+++ b/public/language/ar/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> أضاف مشاركتك في <strong>%2</strong> إلى مفضلته.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> أشعَرَ بمشاركة مخلة في <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "تم التحقق من عنوان البريد الإلكتروني",
     "email-confirmed-message": "شكرًا على إثبات صحة عنوان بريدك الإلكتروني. صار حسابك مفعلًا بالكامل.",
     "email-confirm-error-message": "حدث خطأ أثناء التحقق من عنوان بريدك الإلكتروني. ربما رمز التفعيل خاطئ أو انتهت صلاحيته.",
diff --git a/public/language/ar/pages.json b/public/language/ar/pages.json
index eb40c3e50a..74170e7af3 100644
--- a/public/language/ar/pages.json
+++ b/public/language/ar/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "جاري صيانة %1. المرجو العودة لاحقًا.",
     "maintenance.messageIntro": "بالإضافة إلى ذلك، قام مدبر النظام بترك هذه الرسالة:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json
index f83c82c767..1e66ad91eb 100644
--- a/public/language/ar/topic.json
+++ b/public/language/ar/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "الفئات المعطلة رمادية",
     "confirm_move": "انقل",
     "confirm_fork": "فرع",
-    "favourite": "إضافة إلى المفضلة",
-    "favourites": "المفضلة",
-    "favourites.has_no_favourites": "ليس لديك أي ردود مفضلة. أضف بعض المشاركات إلى المفضلة لرؤيتهم هنا",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "تحميل المزيد من المشاركات",
     "move_topic": "نقل الموضوع",
     "move_topics": "نقل المواضيع",
diff --git a/public/language/ar/uploads.json b/public/language/ar/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/ar/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/ar/user.json b/public/language/ar/user.json
index 577b3e0e25..23519da3a5 100644
--- a/public/language/ar/user.json
+++ b/public/language/ar/user.json
@@ -22,7 +22,7 @@
     "profile": "الملف الشخصي",
     "profile_views": "عدد المشاهدات",
     "reputation": "السمعة",
-    "favourites": "التفضيلات",
+    "favourites": "Bookmarks",
     "watched": "متابع",
     "followers": "المتابعون",
     "following": "يتابع",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "تعديل",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "الصورة المرفوعة",
     "upload_new_picture": "رفع صورة جديدة",
@@ -55,10 +56,11 @@
     "password": "كلمة السر",
     "username_taken_workaround": "اسم المستخدم الذي اخترته سبق أخذه، لذا تم تغييره قليلا. أن الآن مسجل تحت الاسم <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "ارفع الصورة",
     "upload_a_picture": "رفع صورة",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "خيارات",
     "show_email": "أظهر بريدي الإلكتروني",
     "show_fullname": "أظهر اسمي الكامل",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "فتح الروابط الخارجية في نافدة جديدة",
     "enable_topic_searching": "تفعيل خاصية البحث داخل المواضيع",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "متابعة المواضيع التي تقوم بالرد فيها",
     "follow_topics_you_create": "متابعة المواضيع التي تنشئها",
     "grouptitle": "حدد عنوان المجموعة الذي تريد عرضه",
diff --git a/public/language/bg/error.json b/public/language/bg/error.json
index 7c91315b51..0eaeb842d2 100644
--- a/public/language/bg/error.json
+++ b/public/language/bg/error.json
@@ -3,18 +3,18 @@
     "not-logged-in": "Изглежда не сте влезли в системата.",
     "account-locked": "Вашият акаунт беше заключен временно",
     "search-requires-login": "Търсенето изисква акаунт – моля, влезте или се регистрирайте.",
-    "invalid-cid": "Невалиден идентификатор на категория",
-    "invalid-tid": "Невалиден идентификатор на тема",
-    "invalid-pid": "Невалиден идентификатор на публикация",
-    "invalid-uid": "Невалиден идентификатор на потребител",
-    "invalid-username": "Невалидно потребителско име",
-    "invalid-email": "Невалидна е-поща",
-    "invalid-title": "Невалидно заглавие!",
-    "invalid-user-data": "Невалидни потребителски данни",
-    "invalid-password": "Невалидна парола",
+    "invalid-cid": "Грешен идентификатор на категория",
+    "invalid-tid": "Грешен идентификатор на тема",
+    "invalid-pid": "Грешен идентификатор на публикация",
+    "invalid-uid": "Грешен идентификатор на потребител",
+    "invalid-username": "Грешно потребителско име",
+    "invalid-email": "Грешна е-поща",
+    "invalid-title": "Грешно заглавие!",
+    "invalid-user-data": "Грешни потребителски данни",
+    "invalid-password": "Грешна парола",
     "invalid-username-or-password": "Моля, посочете потребителско име и парола",
-    "invalid-search-term": "Невалиден текст за търсене",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-search-term": "Грешен текст за търсене",
+    "invalid-pagination-value": "Грешен номер на страница, трябва да бъде между %1 и %2",
     "username-taken": "Потребителското име е заето",
     "email-taken": "Е-пощата е заета",
     "email-not-confirmed": "Вашата е-поща все още не е потвърдена. Моля, натиснете тук, за да потвърдите е-пощата си.",
@@ -27,6 +27,7 @@
     "password-too-long": "Паролата е твърде дълга",
     "user-banned": "Потребителят е блокиран",
     "user-too-new": "Съжаляваме, но трябва да изчакате поне %1 секунда/и, преди да направите първата си публикация",
+    "blacklisted-ip": "Съжаляваме, но Вашият IP адрес е забранен за ползване в тази общност. Ако смятате, че това е грешка, моля, свържете се с администратор.",
     "no-category": "Категорията не съществува",
     "no-topic": "Темата не съществува",
     "no-post": "Публикацията не съществува",
@@ -50,8 +51,8 @@
     "still-uploading": "Моля, изчакайте качването да приключи.",
     "file-too-big": "Максималният разрешен размер на файл е %1 КБ – моля, качете по-малък файл",
     "guest-upload-disabled": "Качването не е разрешено за гости",
-    "already-favourited": "Вече сте отбелязали тази публикация като любима",
-    "already-unfavourited": "Вече сте премахнали тази публикация от любимите си",
+    "already-favourited": "Вече имате отметка към тази публикация",
+    "already-unfavourited": "Вече сте премахнали отметката си към тази публикация",
     "cant-ban-other-admins": "Не можете да блокирате другите администратори!",
     "cant-remove-last-admin": "Вие сте единственият администратор. Добавете друг потребител като администратор, преди да премахнете себе си като администратор",
     "invalid-image-type": "Грешен тип на изображение. Позволените типове са: %1",
@@ -60,8 +61,8 @@
     "group-name-too-short": "Името на групата е твърде кратко",
     "group-already-exists": "Вече съществува такава група",
     "group-name-change-not-allowed": "Промяната на името на групата не е разрешено",
-    "group-already-member": "Вече членувате в тази група",
-    "group-not-member": "Не членувате в тази група",
+    "group-already-member": "Потребителят вече членува в тази група",
+    "group-not-member": "Потребителят не членува в тази група",
     "group-needs-owner": "Тази група се нуждае от поне един собственик",
     "group-already-invited": "Този потребител вече е бил поканен",
     "group-already-requested": "Вашата заявка за членство вече е била изпратена",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Съобщението е твърде дълго",
     "cant-edit-chat-message": "Нямате право да редактирате това съобщение",
     "cant-remove-last-user": "Не можете да премахнете последния потребител",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Нямате право да изтриете това съобщение",
     "reputation-system-disabled": "Системата за репутация е изключена.",
     "downvoting-disabled": "Отрицателното гласуване е изключено",
     "not-enough-reputation-to-downvote": "Нямате достатъчно репутация, за да гласувате отрицателно за тази публикация",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Моля, използвайте потребителското си име, за да влезете",
     "invite-maximum-met": "Вие сте поканили максимално позволения брой хора (%1 от %2).",
     "no-session-found": "Не е открита сесия за вход!",
-    "not-in-room": "User not in room"
+    "not-in-room": "Потребителят не е в стаята",
+    "no-users-in-room": "Няма потребители в тази стая",
+    "cant-kick-self": "Не можете да изритате себе си от групата"
 }
\ No newline at end of file
diff --git a/public/language/bg/global.json b/public/language/bg/global.json
index 547ae7e519..3236a43698 100644
--- a/public/language/bg/global.json
+++ b/public/language/bg/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "публикувано в %1 %2 от %3",
     "user_posted_ago": "%1 публикува %2",
     "guest_posted_ago": "гост публикува %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "последно редактирано от %1",
     "norecentposts": "Няма скорошни публикации",
     "norecenttopics": "Няма скорошни теми",
     "recentposts": "Скорошни публикации",
@@ -86,5 +86,9 @@
     "delete_all": "Изтриване на всичко",
     "map": "Карта",
     "sessions": "Сесии за вход",
-    "ip_address": "IP адрес"
+    "ip_address": "IP адрес",
+    "enter_page_number": "Въведете номер на страница",
+    "upload_file": "Качване на файл",
+    "upload": "Качване",
+    "allowed-file-types": "Разрешените файлови типове са: %1"
 }
\ No newline at end of file
diff --git a/public/language/bg/groups.json b/public/language/bg/groups.json
index 75870b4fdd..f5ab2262ce 100644
--- a/public/language/bg/groups.json
+++ b/public/language/bg/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Скрита",
     "details.hidden_help": "Ако е включено, тази група няма да бъде извеждана в списъка от групи и потребителите ще трябва да бъдат поканени лично",
     "details.delete_group": "Изтриване на групата",
+    "details.private_system_help": "Частните групи са забранени на системно ниво; тази възможност не върши нищо",
     "event.updated": "Подробностите за групата бяха обновени",
     "event.deleted": "Групата „%1“ беше изтрита",
     "membership.accept-invitation": "Приемане на поканата",
@@ -48,5 +49,6 @@
     "membership.join-group": "Присъединяване към групата",
     "membership.leave-group": "Напускане на групата",
     "membership.reject": "Отхвърляне",
-    "new-group.group_name": "Име на групата:"
+    "new-group.group_name": "Име на групата:",
+    "upload-group-cover": "Качване на снимка за показване на групата"
 }
\ No newline at end of file
diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json
index 1ed5cb50ef..f3afdaaa4a 100644
--- a/public/language/bg/modules.json
+++ b/public/language/bg/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 пише...",
     "chat.user_has_messaged_you": "%1 Ви написа съобщение.",
     "chat.see_all": "Вижте всички разговори",
+    "chat.mark_all_read": "Отбелязване на всички разговори като прочетени",
     "chat.no-messages": "Моля, изберете получател, за да видите историята на съобщенията",
     "chat.no-users-in-room": "Няма потребители в тази стая",
     "chat.recent-chats": "Скорошни разговори",
diff --git a/public/language/bg/notifications.json b/public/language/bg/notifications.json
index 89a389d387..d621942a50 100644
--- a/public/language/bg/notifications.json
+++ b/public/language/bg/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> и %2 други гласуваха положително за Ваша публикация в <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> премести публикацията Ви в <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> премести <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> отбеляза Ваша публикация в <strong>%2</strong> като любима.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> и <strong>%2</strong> отбелязаха Ваша публикация в <strong>%3</strong> като любима.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> и %2 други отбелязаха Ваша публикация в <strong>%3</strong> като любима.",
+    "favourited_your_post_in": "<strong>%1</strong> си запази отметка към Ваша публикация в <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> и <strong>%2</strong> си запазиха отметки към Ваша публикация в <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> и %2 други си запазиха отметки към Ваша публикация в <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> докладва Ваша публикация в <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> и <strong>%2</strong> докладваха Ваша публикация в <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> и %2 други докладваха Ваша публикация в <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> и <strong>%2</strong> започнаха да Ви следват.",
     "user_started_following_you_multiple": "<strong>%1</strong> и %2 започнаха да Ви следват.",
     "new_register": "<strong>%1</strong> изпрати заявка за регистрация.",
+    "new_register_multiple": "Има <strong>%1</strong> заявки за регистрация, които очакват да бъдат прегледани.",
     "email-confirmed": "Е-пощата беше потвърдена",
     "email-confirmed-message": "Благодарим Ви, че потвърдихте е-пощата си. Акаунтът Ви е вече напълно активиран.",
     "email-confirm-error-message": "Възникна проблем при потвърждаването на е-пощата Ви. Може кодът да е грешен или давността му да е изтекла.",
diff --git a/public/language/bg/pages.json b/public/language/bg/pages.json
index 90685e0bef..45e2861bdd 100644
--- a/public/language/bg/pages.json
+++ b/public/language/bg/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Популярните теми този месец",
     "popular-alltime": "Популярните теми за всички времена",
     "recent": "Скорошни теми",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Докладвани публикации",
     "users/online": "Потребители на линия",
     "users/latest": "Последни потребители",
     "users/sort-posts": "Потребители с най-много публикации",
     "users/sort-reputation": "Потребители с най-висока репутация",
-    "users/banned": "Banned Users",
+    "users/banned": "Блокирани потребители",
     "users/search": "Търсене на потребители",
     "notifications": "Известия",
     "tags": "Етикети",
@@ -33,12 +33,13 @@
     "account/posts": "Публикации от %1",
     "account/topics": "Теми, създадени от %1",
     "account/groups": "Групите на %1",
-    "account/favourites": "Любимите публикации на %1",
+    "account/favourites": "Отметнатите публикации на %1",
     "account/settings": "Потребителски настройки",
     "account/watched": "Теми, следени от %1",
     "account/upvoted": "Публикации, получили положителен глас от %1",
     "account/downvoted": "Публикации, получили отрицателен глас от %1",
     "account/best": "Най-добрите публикации от %1",
+    "confirm": "Е-пощата е потвърдена",
     "maintenance.text": "%1 в момента е в профилактика. Моля, върнете се по-късно.",
     "maintenance.messageIntro": "В допълнение, администраторът е оставил това съобщение:",
     "throttled.text": "%1 в момента е недостъпен, поради прекомерно натоварване. Моля, върнете се отново по-късно."
diff --git a/public/language/bg/topic.json b/public/language/bg/topic.json
index e96fb796b9..dbe44a1541 100644
--- a/public/language/bg/topic.json
+++ b/public/language/bg/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Моля, регистрирайте се или влезте, за да се абонирате за тази тема.",
     "markAsUnreadForAll.success": "Темата е отбелязана като непрочетена за всички.",
     "mark_unread": "Отбелязване като непрочетена",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Темата е отбелязана като непрочетена.",
     "watch": "Наблюдаване",
     "unwatch": "Спиране на наблюдаването",
     "watch.title": "Получавайте известия за новите отговори в тази тема",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Изключените категории са засивени",
     "confirm_move": "Преместване",
     "confirm_fork": "Разделяне",
-    "favourite": "Любима",
-    "favourites": "Любими",
-    "favourites.has_no_favourites": "Нямате любими, отбележете няколко публикации, за да ги видите тук!",
+    "favourite": "Отметка",
+    "favourites": "Отметки",
+    "favourites.has_no_favourites": "Все още не сте си запазвали отметки към никакви публикации.",
     "loading_more_posts": "Зареждане на още публикации",
     "move_topic": "Преместване на темата",
     "move_topics": "Преместване на темите",
@@ -105,7 +105,7 @@
     "stale.warning": "Темата, в която отговаряте, е доста стара. Искате ли вместо това да създадете нова и да направите препратка към тази в отговора си?",
     "stale.create": "Създаване на нова тема",
     "stale.reply_anyway": "Отговаряне в тази тема въпреки това",
-    "link_back": "Re: [%1](%2)",
+    "link_back": "Отговор: [%1](%2)",
     "spam": "Спам",
     "offensive": "Обидно",
     "custom-flag-reason": "Изберете причина за докладване"
diff --git a/public/language/bg/uploads.json b/public/language/bg/uploads.json
new file mode 100644
index 0000000000..e262a56d6c
--- /dev/null
+++ b/public/language/bg/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Качване на файла…",
+    "select-file-to-upload": "Изберете файл за качване!",
+    "upload-success": "Файлът е качен успешно!",
+    "maximum-file-size": "Най-много %1 КБ"
+}
\ No newline at end of file
diff --git a/public/language/bg/user.json b/public/language/bg/user.json
index 01ee3779ae..6e16e70603 100644
--- a/public/language/bg/user.json
+++ b/public/language/bg/user.json
@@ -22,7 +22,7 @@
     "profile": "Профил",
     "profile_views": "Преглеждания на профила",
     "reputation": "Репутация",
-    "favourites": "Любими",
+    "favourites": "Отметки",
     "watched": "Наблюдавани",
     "followers": "Последователи",
     "following": "Следва",
@@ -39,6 +39,7 @@
     "change_username": "Промяна на потребителското име",
     "change_email": "Промяна на е-пощата",
     "edit": "Редактиране",
+    "edit-profile": "Редактиране на профила",
     "default_picture": "Иконка по подразбиране",
     "uploaded_picture": "Качена снимка",
     "upload_new_picture": "Качване на нова снимка",
@@ -55,10 +56,11 @@
     "password": "Парола",
     "username_taken_workaround": "Потребителското име, което искате, е заето и затова ние го променихме малко. Вие ще се наричате <strong>%1</strong>",
     "password_same_as_username": "Паролата е същата като потребителското Ви име. Моля, изберете друга парола.",
+    "password_same_as_email": "Паролата е същата като е-пощата Ви. Моля, изберете друга парола.",
     "upload_picture": "Качване на снимка",
     "upload_a_picture": "Качване на снимка",
     "remove_uploaded_picture": "Премахване на качената снимка",
-    "image_spec": "Можете да качвате само файлове във форматите PNG, JPG или BMP",
+    "upload_cover_picture": "Качване на снимка за показване",
     "settings": "Настройки",
     "show_email": "Да се показва е-пощата ми",
     "show_fullname": "Да се показва цялото ми име",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Отваряне на външните връзки в нов подпрозорец",
     "enable_topic_searching": "Включване на търсенето в темите",
     "topic_search_help": "Ако е включено, търсенето в темата ще замени стандартното поведение на браузъра при търсене в страницата и ще Ви позволи да претърсвате цялата тема, а не само това, което се вижда на екрана",
+    "scroll_to_my_post": "След публикуване на отговор, да се показва новата публикация",
     "follow_topics_you_reply_to": "Следване на темите, на които отговаряте",
     "follow_topics_you_create": "Следване на темите, които създавате",
     "grouptitle": "Изберете заглавието на групата, което искате да се показва",
diff --git a/public/language/bg/users.json b/public/language/bg/users.json
index 4a3a288614..3addffa9d9 100644
--- a/public/language/bg/users.json
+++ b/public/language/bg/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Непрочетени теми",
     "categories": "Категории",
     "tags": "Етикети",
-    "no-users-found": "No users found!"
+    "no-users-found": "Няма открити потребители!"
 }
\ No newline at end of file
diff --git a/public/language/bn/error.json b/public/language/bn/error.json
index ca60095dd2..7f363517cc 100644
--- a/public/language/bn/error.json
+++ b/public/language/bn/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "ব্যবহারকারী নিষিদ্ধ",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "বিভাগটি খুজে পাওয়া যায় নি",
     "no-topic": "এই টপিক নেই",
     "no-post": "এই পোষ্ট নেই",
@@ -50,8 +51,8 @@
     "still-uploading": "আপলোড সম্পূর্ণ জন্য অনুগ্রহ করে অপেক্ষা করুন",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "আপনি ইতিমধ্যে এই পোষ্টটি পছন্দের তালিকায় যোগ করেছেন",
-    "already-unfavourited": "আপনি ইতিমধ্যে এই পোষ্টটি আপনার পছন্দের তালিকা থেকে সরিয়ে ফেলেছেন",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "আপনি অন্য এ্যাডমিনদের নিষিদ্ধ করতে পারেন না!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/bn/global.json b/public/language/bn/global.json
index da099cd8e3..9eb59053b1 100644
--- a/public/language/bn/global.json
+++ b/public/language/bn/global.json
@@ -86,5 +86,9 @@
     "delete_all": "সব মুছে ফেলুন",
     "map": "ম্যাপ",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/bn/groups.json b/public/language/bn/groups.json
index 7f95a0e59c..d2752a71e8 100644
--- a/public/language/bn/groups.json
+++ b/public/language/bn/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/bn/modules.json b/public/language/bn/modules.json
index beff4cf9cb..51c65a2a6a 100644
--- a/public/language/bn/modules.json
+++ b/public/language/bn/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 লিখছেন",
     "chat.user_has_messaged_you": "%1 আপনাকে বার্তা পাঠিয়েছেন",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "মেসেজ হিস্টোরী দেখতে প্রাপক নির্বাচন করুন",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "সাম্প্রতিক চ্যাটসমূহ",
diff --git a/public/language/bn/notifications.json b/public/language/bn/notifications.json
index 1411c3b429..497492e0db 100644
--- a/public/language/bn/notifications.json
+++ b/public/language/bn/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "ইমেইল নিশ্চিত করা হয়েছে",
     "email-confirmed-message": "আপনার ইমেইল যাচাই করার জন্য আপনাকে ধন্যবাদ। আপনার অ্যাকাউন্টটি এখন সম্পূর্ণরূপে সক্রিয়।",
     "email-confirm-error-message": "আপনার ইমেল ঠিকানার বৈধতা যাচাইয়ে একটি সমস্যা হয়েছে। সম্ভবত কোডটি ভুল ছিল অথবা কোডের মেয়াদ শেষ হয়ে গিয়েছে।",
diff --git a/public/language/bn/pages.json b/public/language/bn/pages.json
index f59157d907..d7d56d0e4a 100644
--- a/public/language/bn/pages.json
+++ b/public/language/bn/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/bn/topic.json b/public/language/bn/topic.json
index 0c7eedeb73..9fea972e71 100644
--- a/public/language/bn/topic.json
+++ b/public/language/bn/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "নিস্ক্রীয় ক্যাটাগরীসমূহ ধূসর কালিতে লেখা রয়েছে। ",
     "confirm_move": "সরান",
     "confirm_fork": "ফর্ক",
-    "favourite": "পছন্দ",
-    "favourites": "পছন্দতালিকা",
-    "favourites.has_no_favourites": "আপনার যদি কোন পছন্দের পোষ্ট না থেকে থাকে তাহলে কিছু পোষ্ট ফেভারিট করা হলে সেগুলো এখানে দেখতে পাবেন।",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "আরো পোষ্ট লোড করা হচ্ছে",
     "move_topic": "টপিক সরান",
     "move_topics": "টপিক সমূহ সরান",
diff --git a/public/language/bn/uploads.json b/public/language/bn/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/bn/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/bn/user.json b/public/language/bn/user.json
index 9972d6bbf6..9e26660f70 100644
--- a/public/language/bn/user.json
+++ b/public/language/bn/user.json
@@ -22,7 +22,7 @@
     "profile": "প্রোফাইল",
     "profile_views": "প্রোফাইল দেখেছেন",
     "reputation": "সন্মাননা",
-    "favourites": "পছন্দের তালিকা",
+    "favourites": "Bookmarks",
     "watched": "দেখা হয়েছে",
     "followers": "যাদের অনুসরণ করছেন",
     "following": "যারা আপনাকে অনুসরণ করছে",
@@ -39,6 +39,7 @@
     "change_username": "ইউজারনেম পরিবর্তন করুন",
     "change_email": "ইমেইল পরিবর্তন করুন",
     "edit": "সম্পাদনা",
+    "edit-profile": "Edit Profile",
     "default_picture": "ডিফল্ট আইকন",
     "uploaded_picture": "ছবি আপলোড করুন",
     "upload_new_picture": "নতুন ছবি আপলোড করুন",
@@ -55,10 +56,11 @@
     "password": "পাসওয়ার্ড",
     "username_taken_workaround": "আপনি যে ইউজারনেম চাচ্ছিলেন সেটি ইতিমধ্যে নেয়া হয়ে গেছে, কাজেই আমরা এটি কিঞ্চিং পরিবর্তন করেছি। আপনি এখন <strong>%1</strong> হিসেবে পরিচিত",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "ছবি আপলোড করুন",
     "upload_a_picture": "ছবি (একটি) আপলোড করুন",
     "remove_uploaded_picture": "আপলোড করা ছবিটি সরিয়ে নাও",
-    "image_spec": "আপনি শুধুমাত্র PNG, JPG অথবা BMP ফাইল আপলোড করতে পারবেন",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "সেটিংস",
     "show_email": "আমার ইমেইল দেখাও",
     "show_fullname": "আমার সম্পূর্ণ নাম দেখাও",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "আউটগোয়িং লিংকগুলো নতুন ট্যাবে খুলুন",
     "enable_topic_searching": "In-Topic সার্চ সক্রীয় করো",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/cs/error.json b/public/language/cs/error.json
index d3e6559a65..dfec525151 100644
--- a/public/language/cs/error.json
+++ b/public/language/cs/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Uživatel byl zakázán",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategorie neexistuje",
     "no-topic": "Téma neexistuje",
     "no-post": "Příspěvek neexistuje",
@@ -50,8 +51,8 @@
     "still-uploading": "Vyčkejte, prosím, nežli se vše kompletně nahraje.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Nemůžete zakazovat ostatní administrátory!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/cs/global.json b/public/language/cs/global.json
index 6b0d59a86a..1f1900a0f6 100644
--- a/public/language/cs/global.json
+++ b/public/language/cs/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Vymazat vše",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/cs/groups.json b/public/language/cs/groups.json
index d86c1e52f1..5bfd7ed3ec 100644
--- a/public/language/cs/groups.json
+++ b/public/language/cs/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json
index e47a5b1d83..cb37aa7539 100644
--- a/public/language/cs/modules.json
+++ b/public/language/cs/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 píše ...",
     "chat.user_has_messaged_you": "%1 has messaged you.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Please select a recipient to view chat message history",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Recent Chats",
diff --git a/public/language/cs/notifications.json b/public/language/cs/notifications.json
index 3a869b923e..0137a973e3 100644
--- a/public/language/cs/notifications.json
+++ b/public/language/cs/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Confirmed",
     "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
     "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
diff --git a/public/language/cs/pages.json b/public/language/cs/pages.json
index bcae168282..285aa30017 100644
--- a/public/language/cs/pages.json
+++ b/public/language/cs/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json
index e6da76341e..3bced7f182 100644
--- a/public/language/cs/topic.json
+++ b/public/language/cs/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Vypnuté (disabled) kategorie jsou šedé.",
     "confirm_move": "Přesunout",
     "confirm_fork": "Rozdělit",
-    "favourite": "Oblíbené",
-    "favourites": "Oblíbené",
-    "favourites.has_no_favourites": "Nemáte žádné oblíbené příspěvky, přidejte některý příspěvek k oblíbeným a uvidíte ho zde!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Načítání více příspěvků",
     "move_topic": "Přesunout téma",
     "move_topics": "Move Topics",
diff --git a/public/language/cs/uploads.json b/public/language/cs/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/cs/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/cs/user.json b/public/language/cs/user.json
index 0c52c28cc8..35fdc4db9b 100644
--- a/public/language/cs/user.json
+++ b/public/language/cs/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Zobrazení profilu",
     "reputation": "Reputace",
-    "favourites": "Oblíbené",
+    "favourites": "Bookmarks",
     "watched": "Sledován",
     "followers": "Sledují ho",
     "following": "Sleduje",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Upravit",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Nahraný obrázek",
     "upload_new_picture": "Nahrát nový obrázek",
@@ -55,10 +56,11 @@
     "password": "Heslo",
     "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Nahrát obrázek",
     "upload_a_picture": "Nahrát obrázek",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Nastavení",
     "show_email": "Zobrazovat můj email v profilu",
     "show_fullname": "Zobrazovat celé jméno",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/da/email.json b/public/language/da/email.json
index 19fb7776d2..0ed8b77a53 100644
--- a/public/language/da/email.json
+++ b/public/language/da/email.json
@@ -21,9 +21,9 @@
     "digest.cta": "Klik her for at gå til %1",
     "digest.unsub.info": "Du har fået tilsendt dette sammendrag pga. indstillingerne i dit abonnement.",
     "digest.no_topics": "Der har ikke været nogen aktive emner de/den sidste %1",
-    "digest.day": "day",
-    "digest.week": "week",
-    "digest.month": "month",
+    "digest.day": "dag",
+    "digest.week": "uge",
+    "digest.month": "måned",
     "notif.chat.subject": "Ny chat besked modtaget fra %1",
     "notif.chat.cta": "Klik her for at forsætte med samtalen",
     "notif.chat.unsub.info": "Denne chat notifikation blev sendt til dig pga. indstillingerne i dit abonnement.",
diff --git a/public/language/da/error.json b/public/language/da/error.json
index fb66628ba2..148f312c98 100644
--- a/public/language/da/error.json
+++ b/public/language/da/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Ugyldig Adgangskode",
     "invalid-username-or-password": "Venligst angiv både brugernavn og adgangskode",
     "invalid-search-term": "Ugyldig søgeterm",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Ugyldig side værdi, skal mindst være %1 og maks. %2",
     "username-taken": "Brugernavn optaget",
     "email-taken": "Emailadresse allerede i brug",
     "email-not-confirmed": "Din email adresse er ikke blevet bekræftet endnu, venligst klik her for at bekrætige den.",
@@ -24,9 +24,10 @@
     "confirm-email-already-sent": "Bekræftelses email er allerede afsendt, vent venligt %1 minut(ter) for at sende endnu en.",
     "username-too-short": "Brugernavn er for kort",
     "username-too-long": "Brugernavn er for langt",
-    "password-too-long": "Password too long",
+    "password-too-long": "Kodeord er for langt",
     "user-banned": "Bruger er bortvist",
     "user-too-new": "Beklager, du er nødt til at vente %1 sekund(er) før du opretter dit indlæg",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategorien eksisterer ikke",
     "no-topic": "Tråden eksisterer ikke",
     "no-post": "Indlægget eksisterer ikke",
@@ -49,9 +50,9 @@
     "too-many-tags": "For mange tags. Tråde kan ikke have mere end %1 tag(s)",
     "still-uploading": "Venligst vent til overførslen er færdig",
     "file-too-big": "Maksimum filstørrelse er %1 kB - venligst overfør en mindre fil",
-    "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Du har allerede føjet dette indlæg til dine favoritter",
-    "already-unfavourited": "Du har allerede fjernet dette indlæg fra dine favoritter",
+    "guest-upload-disabled": "Gæsteupload er deaktiveret",
+    "already-favourited": "Du har allerede bogmærket dette indlæg",
+    "already-unfavourited": "Du har allerede fjernet dette indlæg fra bogmærker",
     "cant-ban-other-admins": "Du kan ikke udlukke andre administatrorer!",
     "cant-remove-last-admin": "Du er den eneste administrator. Tilføj en anden bruger som administrator før du fjerner dig selv som administrator",
     "invalid-image-type": "Invalid billed type. De tilladte typer er: %1",
@@ -77,13 +78,13 @@
     "about-me-too-long": "Beklager, men din om mig side kan ikke være længere end %1 karakter(er).",
     "cant-chat-with-yourself": "Du kan ikke chatte med dig selv!",
     "chat-restricted": "Denne bruger har spæret adgangen til chat beskeder. Brugeren må følge dig før du kan chatte med ham/hende",
-    "chat-disabled": "Chat system disabled",
+    "chat-disabled": "Chat system er deaktiveret",
     "too-many-messages": "Du har sendt for mange beskeder, vent venligt lidt.",
     "invalid-chat-message": "Ugyldig chat besked",
     "chat-message-too-long": "Chat beskeden er for lang",
-    "cant-edit-chat-message": "You are not allowed to edit this message",
-    "cant-remove-last-user": "You can't remove the last user",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-edit-chat-message": "Du har ikke tilladelse til at redigere denne besked",
+    "cant-remove-last-user": "Du kan ikke fjerne den sidste bruger",
+    "cant-delete-chat-message": "Du har ikke tilladelse til at slette denne besked",
     "reputation-system-disabled": "Vurderingssystem er slået fra.",
     "downvoting-disabled": "Nedvurdering er slået fra",
     "not-enough-reputation-to-downvote": "Du har ikke nok omdømme til at nedstemme dette indlæg",
@@ -94,7 +95,9 @@
     "parse-error": "Noget gik galt under fortolknings er serverens respons",
     "wrong-login-type-email": "Brug venligt din email til login",
     "wrong-login-type-username": "Brug venligt dit brugernavn til login",
-    "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
-    "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "invite-maximum-met": "Du har inviteret det maksimale antal personer (%1 ud af %2)",
+    "no-session-found": "Ingen login session kan findes!",
+    "not-in-room": "Bruger er ikke i rummet",
+    "no-users-in-room": "Ingen brugere i rummet",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/da/global.json b/public/language/da/global.json
index a6a29873ab..5990eb4da5 100644
--- a/public/language/da/global.json
+++ b/public/language/da/global.json
@@ -49,9 +49,9 @@
     "users": "Bruger",
     "topics": "Emner",
     "posts": "Indlæg",
-    "best": "Best",
-    "upvoted": "Upvoted",
-    "downvoted": "Downvoted",
+    "best": "Bedste",
+    "upvoted": "Syntes godt om",
+    "downvoted": "Syntes ikke godt om",
     "views": "Visninger",
     "reputation": "Omdømme",
     "read_more": "læs mere",
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "skrevet i %1 %2 af %3",
     "user_posted_ago": "%1 skrev for %2",
     "guest_posted_ago": "Gæst skrev for %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "sidst redigeret af %1",
     "norecentposts": "Ingen seneste indlæg",
     "norecenttopics": "Ingen seneste tråde",
     "recentposts": "Seneste indlæg",
@@ -85,6 +85,10 @@
     "unfollow": "Følg ikke længere",
     "delete_all": "Slet alt",
     "map": "Kort",
-    "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "sessions": "Login Sessioner",
+    "ip_address": "IP-adresse",
+    "enter_page_number": "Indsæt sideantal",
+    "upload_file": "Upload fil",
+    "upload": "Upload",
+    "allowed-file-types": "Tilladte filtyper er %1"
 }
\ No newline at end of file
diff --git a/public/language/da/groups.json b/public/language/da/groups.json
index eda84cdb9e..f02ec79938 100644
--- a/public/language/da/groups.json
+++ b/public/language/da/groups.json
@@ -24,7 +24,7 @@
     "details.has_no_posts": "Medlemmer af denne gruppe har ikke oprettet indlæg.",
     "details.latest_posts": "seneste indlæg",
     "details.private": "Privat",
-    "details.disableJoinRequests": "Disable join requests",
+    "details.disableJoinRequests": "Deaktiver Anmodninger",
     "details.grant": "Giv/ophæv ejerskab",
     "details.kick": "Spark",
     "details.owner_options": "Gruppe administration",
@@ -41,6 +41,7 @@
     "details.hidden": "Skjult",
     "details.hidden_help": "Hvis aktiveret, så vil denne gruppe ikke kunne ses i gruppelisten og bruhere skal inviteres manuelt",
     "details.delete_group": "Slet Gruppe",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Gruppe detaljer er blevet opdateret",
     "event.deleted": "Gruppen \"%1\" er blevet slettet",
     "membership.accept-invitation": "Acceptér Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Bliv medlem af gruppe",
     "membership.leave-group": "Forlad Gruppe",
     "membership.reject": "Afvis",
-    "new-group.group_name": "Gruppe Navn:"
+    "new-group.group_name": "Gruppe Navn:",
+    "upload-group-cover": "Upload Gruppe coverbillede"
 }
\ No newline at end of file
diff --git a/public/language/da/modules.json b/public/language/da/modules.json
index 114d1cd386..31ea913abe 100644
--- a/public/language/da/modules.json
+++ b/public/language/da/modules.json
@@ -6,8 +6,9 @@
     "chat.user_typing": "%1 skriver ...",
     "chat.user_has_messaged_you": "1% har skrevet til dig.",
     "chat.see_all": "Se alle chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Vælg en modtager for at se beskedhistorikken",
-    "chat.no-users-in-room": "No users in this room",
+    "chat.no-users-in-room": "Ingen brugere i rummet",
     "chat.recent-chats": "Seneste chats",
     "chat.contacts": "Kontakter",
     "chat.message-history": "Beskedhistorik",
@@ -16,9 +17,9 @@
     "chat.seven_days": "7 dage",
     "chat.thirty_days": "30 dage",
     "chat.three_months": "3 måneder",
-    "chat.delete_message_confirm": "Are you sure you wish to delete this message?",
-    "chat.roomname": "Chat Room %1",
-    "chat.add-users-to-room": "Add users to room",
+    "chat.delete_message_confirm": "Er du sikker på at du vil slette denne besked?",
+    "chat.roomname": "Chatrum %1",
+    "chat.add-users-to-room": "Tilføj brugere til chatrum",
     "composer.compose": "Skriv",
     "composer.show_preview": "Vis forhåndsvisning",
     "composer.hide_preview": "Fjern forhåndsvisning",
@@ -31,7 +32,7 @@
     "bootbox.ok": "OK",
     "bootbox.cancel": "Annuller",
     "bootbox.confirm": "Bekræft",
-    "cover.dragging_title": "Cover Photo Positioning",
-    "cover.dragging_message": "Drag the cover photo to the desired position and click \"Save\"",
-    "cover.saved": "Cover photo image and position saved"
+    "cover.dragging_title": "Coverbillede positionering ",
+    "cover.dragging_message": "Træk coverbilledet til den ønskede position og klik \"Gem\"",
+    "cover.saved": "Coverbillede og position gemt "
 }
\ No newline at end of file
diff --git a/public/language/da/notifications.json b/public/language/da/notifications.json
index 077e72b553..0e36cb2149 100644
--- a/public/language/da/notifications.json
+++ b/public/language/da/notifications.json
@@ -5,31 +5,32 @@
     "mark_all_read": "Marker alle notifikationer læst",
     "back_to_home": "Tilbage til %1",
     "outgoing_link": "Udgående link",
-    "outgoing_link_message": "You are now leaving %1",
+    "outgoing_link_message": "Du forlader nu %1",
     "continue_to": "Fortsæt til %1",
     "return_to": "Returnere til %t",
     "new_notification": "Ny notifikation",
     "you_have_unread_notifications": "Du har ulæste notifikationer.",
     "new_message_from": "Ny besked fra <strong>%1</strong>",
     "upvoted_your_post_in": "<strong>%1</strong> har upvotet dit indlæg i <strong>%2</strong>.",
-    "upvoted_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have upvoted your post in <strong>%3</strong>.",
-    "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
+    "upvoted_your_post_in_dual": "<strong>%1</strong> og <strong>%2</strong> har syntes godt om dit indlæg i <strong>%3</strong>.",
+    "upvoted_your_post_in_multiple": "<strong>%1</strong> og %2 andre har syntes godt om dit indlæg i<strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> har flyttet dit indlæg til <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> har flyttet <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> har favoriseret dit indlæg i <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> har bogmærket dit indlæg i <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> og <strong>%2</strong> har bogmærket dit indlæg i <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> og %2 andre har bogmærket dit indlæg i <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> har anmeldt et indlæg i <strong>%2</strong>",
-    "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
-    "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
+    "user_flagged_post_in_dual": "<strong>%1</strong> og <strong>%2</strong> har anmeldt et indlæg i <strong>%3</strong>",
+    "user_flagged_post_in_multiple": "<strong>%1</strong> og %2 andre har anmeldt et indlæg i <strong>%3</strong>",
     "user_posted_to": "<strong>%1</strong> har skrevet et svar til: <strong>%2</strong>",
-    "user_posted_to_dual": "<strong>%1</strong> and <strong>%2</strong> have posted replies to: <strong>%3</strong>",
-    "user_posted_to_multiple": "<strong>%1</strong> and %2 others have posted replies to: <strong>%3</strong>",
+    "user_posted_to_dual": "<strong>%1</strong> og <strong>%2</strong> har skrevet svar til: <strong>%3</strong>",
+    "user_posted_to_multiple": "<strong>%1</strong> og %2 andre har skrevet svar til: <strong>%3</strong>",
     "user_posted_topic": "<strong>%1</strong> har oprettet en ny tråd: <strong>%2</strong>",
     "user_started_following_you": "<strong>%1</strong> har valgt at følge dig.",
-    "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
-    "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
+    "user_started_following_you_dual": "<strong>%1</strong> og <strong>%2</strong> har valgt at følge dig.",
+    "user_started_following_you_multiple": "<strong>%1</strong> og %2 har valgt at følge dig.",
     "new_register": "<strong>%1</strong> har sendt en registrerings anmodning.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email bekræftet",
     "email-confirmed-message": "Tak fordi du validerede din email. Din konto er nu fuldt ud aktiveret.",
     "email-confirm-error-message": "Der var et problem med valideringen af din emailadresse. Bekræftelses koden var muligvis forkert eller udløbet.",
diff --git a/public/language/da/pages.json b/public/language/da/pages.json
index 11409c5109..cfc7678ce8 100644
--- a/public/language/da/pages.json
+++ b/public/language/da/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Populære tråde denne måned",
     "popular-alltime": "Top populære tråde",
     "recent": "Seneste tråde",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Anmeldte Indlæg",
     "users/online": "Online brugere",
     "users/latest": "Seneste brugere",
     "users/sort-posts": "Brugere med de fleste indlæg",
     "users/sort-reputation": "Brugere med mest omdømme",
-    "users/banned": "Banned Users",
+    "users/banned": "Banlyste Brugere",
     "users/search": "Bruger søgning",
     "notifications": "Notifikationer",
     "tags": "Tags",
@@ -33,12 +33,13 @@
     "account/posts": "Indlæg oprettet af %1",
     "account/topics": "Tråde lavet af %1",
     "account/groups": "%1s grupper",
-    "account/favourites": "&1s favorit indlæg",
+    "account/favourites": "%1's Bogmærkede Indlæg",
     "account/settings": "Bruger instillinger",
     "account/watched": "Tråde fulgt af %1",
-    "account/upvoted": "Posts upvoted by %1",
-    "account/downvoted": "Posts downvoted by %1",
-    "account/best": "Best posts made by %1",
+    "account/upvoted": "Indlæg syntes godt om af %1",
+    "account/downvoted": "Indlæg syntes ikke godt om af %1",
+    "account/best": "Bedste indlæg skrevet af %1",
+    "confirm": "Email Bekræftet",
     "maintenance.text": "%1 er under vedligeholdelse. Kom venligst tilbage senere.",
     "maintenance.messageIntro": "Administratoren har yderligere vedlagt denne besked:",
     "throttled.text": "%1 er ikke tilgængelig på grund af overbelastning. Venligst kom tilbage senere."
diff --git a/public/language/da/topic.json b/public/language/da/topic.json
index 3ad278b495..888384ced3 100644
--- a/public/language/da/topic.json
+++ b/public/language/da/topic.json
@@ -13,7 +13,7 @@
     "notify_me": "Bliv notificeret ved nye svar i dette emne",
     "quote": "Citer",
     "reply": "Svar",
-    "reply-as-topic": "Reply as topic",
+    "reply-as-topic": "Svar som emne",
     "guest-login-reply": "Login for at svare",
     "edit": "Rediger",
     "delete": "Slet",
@@ -34,8 +34,8 @@
     "not_following_topic.message": "Du vil ikke længere modtage notifikationer fra dette emne.",
     "login_to_subscribe": "Venligt registrer eller login for at abbonere på dette emne.",
     "markAsUnreadForAll.success": "Emnet er market ulæst for alle.",
-    "mark_unread": "Mark unread",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread": "Marker ulæste",
+    "mark_unread.success": "Emne markeret som ulæst.",
     "watch": "Overvåg",
     "unwatch": "Fjern overvågning",
     "watch.title": "Bliv notificeret ved nye indlæg i dette emne",
@@ -51,7 +51,7 @@
     "thread_tools.move_all": "Flyt alt",
     "thread_tools.fork": "Fraskil tråd",
     "thread_tools.delete": "Slet tråd",
-    "thread_tools.delete-posts": "Delete Posts",
+    "thread_tools.delete-posts": "Slet Indlæg",
     "thread_tools.delete_confirm": "Er du sikker på at du vil slette dette emne?",
     "thread_tools.restore": "Gendan tråd",
     "thread_tools.restore_confirm": "Er du sikker på at du ønsker at genoprette denne tråd?",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Deaktiverede kategorier er nedtonede",
     "confirm_move": "Flyt",
     "confirm_fork": "Fraskil",
-    "favourite": "Favoriser",
-    "favourites": "Favoritter",
-    "favourites.has_no_favourites": "Du har ingen favoritter, favoriser nogle indlæg for at se dem her!",
+    "favourite": "Bogmærke",
+    "favourites": "Bogmærker",
+    "favourites.has_no_favourites": "Du har ikke tilføjet nogle indlæg til dine bogmærker endnu.",
     "loading_more_posts": "Indlæser flere indlæg",
     "move_topic": "Flyt tråd",
     "move_topics": "Flyt tråde",
@@ -78,7 +78,7 @@
     "fork_topic_instruction": "Klik på indlæg du ønsker at fraskille",
     "fork_no_pids": "Ingen indlæg valgt",
     "fork_success": "Tråden blev fraskilt! Klik her for at gå til den fraskilte tråd.",
-    "delete_posts_instruction": "Click the posts you want to delete/purge",
+    "delete_posts_instruction": "Klik på de indlæg du vil slette/rense",
     "composer.title_placeholder": "Angiv din trådtittel her ...",
     "composer.handle_placeholder": "Navn",
     "composer.discard": "Fortryd",
@@ -101,12 +101,12 @@
     "newest_to_oldest": "Nyeste til ældste",
     "most_votes": "Flest stemmer",
     "most_posts": "Flest indlæg",
-    "stale.title": "Create new topic instead?",
-    "stale.warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?",
-    "stale.create": "Create a new topic",
-    "stale.reply_anyway": "Reply to this topic anyway",
-    "link_back": "Re: [%1](%2)",
+    "stale.title": "Opret nyt emne istedet?",
+    "stale.warning": "Emnet du svarer på er ret gammelt. Vil du oprette et nyt emne istedet og referere dette indlæg i dit svar?",
+    "stale.create": "Opret nyt emne",
+    "stale.reply_anyway": "Svar dette emne alligevel",
+    "link_back": "Svar: [%1](%2)",
     "spam": "Spam",
     "offensive": "Stødende",
-    "custom-flag-reason": "Enter a flagging reason"
+    "custom-flag-reason": "Indsæt en markeringsgrund"
 }
\ No newline at end of file
diff --git a/public/language/da/uploads.json b/public/language/da/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/da/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/da/user.json b/public/language/da/user.json
index edd05211e1..ebc1cba887 100644
--- a/public/language/da/user.json
+++ b/public/language/da/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Profil visninger",
     "reputation": "Omdømme",
-    "favourites": "Favoritter",
+    "favourites": "Bogmærker",
     "watched": "Set",
     "followers": "Followers",
     "following": "Følger",
@@ -39,6 +39,7 @@
     "change_username": "Ændre brugernavn",
     "change_email": "Ændre email",
     "edit": "Rediger",
+    "edit-profile": "Edit Profile",
     "default_picture": "Standard ikon",
     "uploaded_picture": "Upload billede",
     "upload_new_picture": "Upload nyt billede",
@@ -55,10 +56,11 @@
     "password": "Kodeord",
     "username_taken_workaround": "Det valgte brugernavn er allerede taget, så vi har ændret det en smule. Du hedder nu <strong>%1</strong>",
     "password_same_as_username": "Din adgangskode er det samme som dit brugernavn, vælg venligst en anden adgangskode.",
+    "password_same_as_email": "Dit kodeord er det samme som din email, venligst vælg et andet kodeord",
     "upload_picture": "Upload billede",
     "upload_a_picture": "Upload et billede",
     "remove_uploaded_picture": "Fjern uploaded billede",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload coverbillede",
     "settings": "Indstillinger",
     "show_email": "Vis min emailaddresse",
     "show_fullname": "Vis mit fulde navn",
@@ -77,9 +79,9 @@
     "has_no_posts": "Denne bruger har ikke skrevet noget endnu.",
     "has_no_topics": "Denne bruger har ikke skrævet nogle tråde endnu.",
     "has_no_watched_topics": "Denne bruger har ikke fulgt nogle tråde endnu.",
-    "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.",
-    "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.",
-    "has_no_voted_posts": "This user has no voted posts",
+    "has_no_upvoted_posts": "Denne bruger har ikke syntes godt om nogle indlæg endnu.",
+    "has_no_downvoted_posts": "Denne bruger har ikke, syntes ikke godt om nogle indlæg endnu.",
+    "has_no_voted_posts": "Denne bruger har ingen stemte indlæg",
     "email_hidden": "Email Skjult",
     "hidden": "skjult",
     "paginate_description": "Sideinddel emner og indlæg istedet for uendeligt rul",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Åben udgående link i en ny tab",
     "enable_topic_searching": "Slå In-Topic søgning til",
     "topic_search_help": "Hvis slået til, så vil in-topic søgning overskrive browserens almindelige søge function og tillade dig at søge hele emnet, istedet for kun det der er vist på skærmen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Følg emner du har skrevet indlæg i",
     "follow_topics_you_create": "Følg emner du opretter",
     "grouptitle": "Vælg gruppe titlen du gerne vil fremvise",
@@ -98,9 +101,9 @@
     "select-homepage": "Vælg en hjemmeside",
     "homepage": "Hjemmeside",
     "homepage_description": "Vælg en side som forummets hjemmeside, eller 'Ingen' for at bruge standard hjemmesiden.",
-    "custom_route": "Custom Homepage Route",
-    "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")",
-    "sso.title": "Single Sign-on Services",
-    "sso.associated": "Associated with",
-    "sso.not-associated": "Click here to associate with"
+    "custom_route": "Brugerdefinerede hjemme rute",
+    "custom_route_help": "Indtast et rute navn her, uden nogle foregående skråstreg (f.eks. \"nyligt\" eller \"populært\")",
+    "sso.title": "Enkeltgangs Sign-on Servicer",
+    "sso.associated": "Forbundet med",
+    "sso.not-associated": "Klik her for at forbinde med"
 }
\ No newline at end of file
diff --git a/public/language/da/users.json b/public/language/da/users.json
index 8ec326f45f..71e4aef9f7 100644
--- a/public/language/da/users.json
+++ b/public/language/da/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Ulæste Tråde",
     "categories": "Kategorier",
     "tags": "Tags",
-    "no-users-found": "No users found!"
+    "no-users-found": "Ingen brugere fundet!"
 }
\ No newline at end of file
diff --git a/public/language/de/email.json b/public/language/de/email.json
index 3df33a6702..459cb40156 100644
--- a/public/language/de/email.json
+++ b/public/language/de/email.json
@@ -14,10 +14,10 @@
     "reset.text2": "Klicke bitte auf den folgenden Link, um mit der Zurücksetzung deines Passworts fortzufahren:",
     "reset.cta": "Klicke hier, um dein Passwort zurückzusetzen",
     "reset.notify.subject": "Passwort erfolgreich geändert",
-    "reset.notify.text1": "Wir benachrichtigen dich das am %1, dein Passwort erfolgreich geändert wurde.",
-    "reset.notify.text2": "Wenn du das nicht autorisiert hast, bitte benachrichtige umgehend einen Administrator.",
+    "reset.notify.text1": "Wir benachrichtigen dich, dass dein Passwort am %1 erfolgreich geändert wurde.",
+    "reset.notify.text2": "Bitte benachrichtige umgehend einen Administrator, wenn du dies nicht autorisiert hast.",
     "digest.notifications": "Du hast ungelesene Benachrichtigungen von %1:",
-    "digest.latest_topics": "Neueste Themen vom %1",
+    "digest.latest_topics": "Neueste Themen auf %1",
     "digest.cta": "Klicke hier, um %1 zu besuchen",
     "digest.unsub.info": "Diese Zusammenfassung wurde dir aufgrund deiner Abonnement-Einstellungen gesendet.",
     "digest.no_topics": "Es gab keine aktiven Themen innerhalb %1",
diff --git a/public/language/de/error.json b/public/language/de/error.json
index 32a56b5d64..40615e0e2e 100644
--- a/public/language/de/error.json
+++ b/public/language/de/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Ungültiges Passwort",
     "invalid-username-or-password": "Bitte gebe einen Benutzernamen und ein Passwort an",
     "invalid-search-term": "Ungültige Suchanfrage",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Ungültige Seitennummerierung, muss mindestens %1 und maximal %2 sein",
     "username-taken": "Der Benutzername ist bereits vergeben",
     "email-taken": "Die E-Mail-Adresse ist bereits vergeben",
     "email-not-confirmed": "Deine E-Mail wurde noch nicht bestätigt, bitte klicke hier, um deine E-Mail zu bestätigen.",
@@ -27,6 +27,7 @@
     "password-too-long": "Passwort ist zu lang",
     "user-banned": "Benutzer ist gesperrt",
     "user-too-new": "Entschuldigung, du musst %1 Sekunde(n) warten, bevor du deinen ersten Beitrag schreiben kannst.",
+    "blacklisted-ip": "Deine IP-Adresse ist für diese Plattform gesperrt. Sollte dies ein Irrtum sein, dann kontaktiere bitte einen Administrator.",
     "no-category": "Die Kategorie existiert nicht",
     "no-topic": "Das Thema existiert nicht",
     "no-post": "Der Beitrag existiert nicht",
@@ -50,8 +51,8 @@
     "still-uploading": "Bitte warte bis der Vorgang abgeschlossen ist.",
     "file-too-big": "Die maximale Dateigröße ist %1 kB, bitte lade eine kleinere Datei hoch.",
     "guest-upload-disabled": "Uploads für Gäste wurden deaktiviert.",
-    "already-favourited": "Dieser Beitrag ist bereits in deinen Favoriten enthalten",
-    "already-unfavourited": "Du hast diesen Beitrag bereits aus deinen Favoriten entfernt",
+    "already-favourited": "Du hast diesen Beitrag bereits als Lesezeichen gespeichert",
+    "already-unfavourited": "Du hast diesen Beitrag bereits aus deinen Lesezeichen entfernt",
     "cant-ban-other-admins": "Du kannst andere Administratoren nicht sperren!",
     "cant-remove-last-admin": "Du bist der einzige Administrator. Füge zuerst einen anderen Administrator hinzu, bevor du dich selbst als Administrator entfernst",
     "invalid-image-type": "Falsche Bildart. Erlaubte Arten sind: %1",
@@ -60,7 +61,7 @@
     "group-name-too-short": "Gruppenname zu kurz",
     "group-already-exists": "Gruppe existiert bereits",
     "group-name-change-not-allowed": "Du kannst den Namen der Gruppe nicht ändern",
-    "group-already-member": "Du bist bereits Teil dieser Gruppe",
+    "group-already-member": "Bereits Teil dieser Gruppe",
     "group-not-member": "Du bist kein Mitglied dieser Gruppe",
     "group-needs-owner": "Diese Gruppe muss mindestens einen Besitzer vorweisen",
     "group-already-invited": "Dieser Benutzer wurde bereits eingeladen",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Die Nachricht ist zu lang",
     "cant-edit-chat-message": "Du darfst diese Nachricht nicht ändern",
     "cant-remove-last-user": "Du kannst den letzten Benutzer nicht entfernen",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Du darfst diese Nachricht nicht löschen",
     "reputation-system-disabled": "Das Reputationssystem ist deaktiviert.",
     "downvoting-disabled": "Downvotes sind deaktiviert.",
     "not-enough-reputation-to-downvote": "Dein Ansehen ist zu niedrig, um diesen Beitrag negativ zu bewerten.",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Bitte nutze deinen Benutzernamen zum einloggen",
     "invite-maximum-met": "Du hast bereits die maximale Anzahl an Personen eingeladen (%1 von %2).",
     "no-session-found": "Keine Login-Sitzung gefunden!",
-    "not-in-room": "User not in room"
+    "not-in-room": "Benutzer nicht im Raum",
+    "no-users-in-room": "In diesem Raum befinden sich keine Benutzer.",
+    "cant-kick-self": "Du kannst dich nicht selber aus der Gruppe entfernen."
 }
\ No newline at end of file
diff --git a/public/language/de/global.json b/public/language/de/global.json
index 0cd14600ce..82179436b4 100644
--- a/public/language/de/global.json
+++ b/public/language/de/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "Verfasst in %1 %2 von %3",
     "user_posted_ago": "%1 schrieb %2",
     "guest_posted_ago": "Gast schrieb %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "zuletzt editiert von %1",
     "norecentposts": "Keine aktuellen Beiträge",
     "norecenttopics": "Keine aktuellen Themen",
     "recentposts": "Aktuelle Beiträge",
@@ -86,5 +86,9 @@
     "delete_all": "Alles löschen",
     "map": "Karte",
     "sessions": "Login-Sitzungen",
-    "ip_address": "IP-Adresse"
+    "ip_address": "IP-Adresse",
+    "enter_page_number": "Seitennummer eingeben",
+    "upload_file": "Datei hochladen",
+    "upload": "Hochladen",
+    "allowed-file-types": "Erlaubte Dateitypen sind %1"
 }
\ No newline at end of file
diff --git a/public/language/de/groups.json b/public/language/de/groups.json
index c85d80412e..5d328ac70d 100644
--- a/public/language/de/groups.json
+++ b/public/language/de/groups.json
@@ -8,7 +8,7 @@
     "pending.reject": "Abweisen",
     "pending.accept_all": "Alle annehmen",
     "pending.reject_all": "Alle ablehnen",
-    "pending.none": "Es sind zur Zeit keine unvearbeiteten Mitglieder vorhanden",
+    "pending.none": "Es gibt zur Zeit keine ausstehende Mitglieder",
     "invited.none": "Es sind zur Zeit keine weiteren Mitglieder eingeladen",
     "invited.uninvite": "Einladung zurücknehmen",
     "invited.search": "Suche nach einem Benutzer um ihn in diese Gruppe aufzunehmen",
@@ -41,6 +41,7 @@
     "details.hidden": "Versteckt",
     "details.hidden_help": "Wenn aktiviert, wird diese Gruppe in der Gruppenliste nicht zu finden sein, und Benutzer werden manuell eingeladen werden müssen.",
     "details.delete_group": "Gruppe löschen",
+    "details.private_system_help": "Private Gruppen wurden systemweit deaktiviert. Diese Einstellung hat keine Funktion.",
     "event.updated": "Gruppendetails wurden aktualisiert",
     "event.deleted": "Die Gruppe \"%1\" wurde gelöscht.",
     "membership.accept-invitation": "Einladung akzeptieren",
@@ -48,5 +49,6 @@
     "membership.join-group": "Gruppe beitreten",
     "membership.leave-group": "Gruppe verlassen",
     "membership.reject": "Ablehnen",
-    "new-group.group_name": "Gruppenname:"
+    "new-group.group_name": "Gruppenname:",
+    "upload-group-cover": "Gruppentitelbild hochladen"
 }
\ No newline at end of file
diff --git a/public/language/de/modules.json b/public/language/de/modules.json
index 339bb00f68..2210c035fd 100644
--- a/public/language/de/modules.json
+++ b/public/language/de/modules.json
@@ -5,7 +5,8 @@
     "chat.no_active": "Du hast keine aktiven Chats.",
     "chat.user_typing": "%1 tippt gerade ...",
     "chat.user_has_messaged_you": "%1 hat dir geschrieben.",
-    "chat.see_all": "Alle Chats sehen",
+    "chat.see_all": "Alle Chats anzeigen",
+    "chat.mark_all_read": "Alle als gelesen markieren",
     "chat.no-messages": "Bitte wähle einen Empfänger, um den jeweiligen Nachrichtenverlauf anzuzeigen.",
     "chat.no-users-in-room": "In diesem Raum befinden sich keine Benutzer.",
     "chat.recent-chats": "Aktuelle Chats",
diff --git a/public/language/de/notifications.json b/public/language/de/notifications.json
index 1657269f9a..51cb09b719 100644
--- a/public/language/de/notifications.json
+++ b/public/language/de/notifications.json
@@ -1,8 +1,8 @@
 {
     "title": "Benachrichtigungen",
     "no_notifs": "Keine neuen Benachrichtigungen",
-    "see_all": "Alle Benachrichtigungen zeigen",
-    "mark_all_read": "Alle Benachrichtigungen als gelesen markieren",
+    "see_all": "Alle Benachrichtigungen anzeigen",
+    "mark_all_read": "Alle als gelesen markieren",
     "back_to_home": "Zurück zu %1",
     "outgoing_link": "Externer Link",
     "outgoing_link_message": "Du verlässt nun %1",
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> und %2 andere Nutzer haben deinen Beitrag in <strong>%3</strong> positiv bewertet.",
     "moved_your_post": "<strong>%1</strong> hat deinen Beitrag nach <strong>%2</strong> verschoben.",
     "moved_your_topic": "<strong>%1</strong> hat <strong>%2</strong> verschoben.",
-    "favourited_your_post_in": "<strong>%1</strong> hat deinen Beitrag in <strong>%2</strong> favorisiert.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> und <strong>%2</strong> haben deinen Beitrag in <strong>%3</strong> favorisiert.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> und %2 andere Nutzer haben deinen Beitrag in <strong>%3</strong> favorisiert.",
+    "favourited_your_post_in": "<strong>%1</strong> hat deinen Beitrag in <strong>%2</strong> als Lesezeichen gespeichert.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> und <strong>%2</strong> haben deinen Beitrag in <strong>%3</strong> als Lesezeichen gespeichert.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> und %2 andere Nutzer haben deinen Beitrag in <strong>%3</strong> als Lesezeichen gespeichert.",
     "user_flagged_post_in": "<strong>%1</strong> hat einen Beitrag in </strong>%2</strong> gemeldet",
     "user_flagged_post_in_dual": "<strong>%1</strong> und <strong>%2</strong> haben einen Beitrag in <strong>%3</strong> gemeldet",
     "user_flagged_post_in_multiple": "<strong>%1</strong> und %2 andere Nutzer haben einen Beitrag in <strong>%3</strong> gemeldet",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> und <strong>%2</strong> folgen dir jetzt.",
     "user_started_following_you_multiple": "<strong>%1</strong> und %2 andere Nutzer folgen dir jetzt.",
     "new_register": "<strong>%1</strong> hat eine Registrationsanfrage geschickt.",
+    "new_register_multiple": "Es erwarten <strong>%1</strong>  Registrierungsanfragen eine Überprüfung.",
     "email-confirmed": "E-Mail bestätigt",
     "email-confirmed-message": "Vielen Dank für Ihre E-Mail-Validierung. Ihr Konto ist nun vollständig aktiviert.",
     "email-confirm-error-message": "Es gab ein Problem bei der Validierung Ihrer E-Mail-Adresse. Möglicherweise ist der Code ungültig oder abgelaufen.",
diff --git a/public/language/de/pages.json b/public/language/de/pages.json
index 42fc17b802..743f286bd4 100644
--- a/public/language/de/pages.json
+++ b/public/language/de/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Beliebte Themen dieses Monats",
     "popular-alltime": "Beliebteste Themen",
     "recent": "Neueste Themen",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Gemeldete Beiträge",
     "users/online": "Benutzer online",
     "users/latest": "Neuste Benutzer",
     "users/sort-posts": "Benutzer mit den meisten Beiträgen",
     "users/sort-reputation": "Benutzer mit dem höchsten Ansehen",
-    "users/banned": "Banned Users",
+    "users/banned": "Gesperrte Benutzer",
     "users/search": "Benutzer Suche",
     "notifications": "Benachrichtigungen",
     "tags": "Markierungen",
@@ -31,14 +31,15 @@
     "account/following": "Nutzer, denen %1 folgt",
     "account/followers": "Nutzer, die %1 folgen",
     "account/posts": "Beiträge von %1",
-    "account/topics": "Themen verfasst von %1",
+    "account/topics": "Von %1 verfasste Themen",
     "account/groups": "Gruppen von %1",
-    "account/favourites": "Von %1 favorisierte Beiträge",
+    "account/favourites": "Lesezeichen von %1",
     "account/settings": "Benutzer-Einstellungen",
-    "account/watched": "Themen angeschaut von %1",
+    "account/watched": "Von %1 beobachtete Themen",
     "account/upvoted": "Von %1 positiv bewertete Beiträge",
     "account/downvoted": "Von %1 negativ bewertete Beiträge",
     "account/best": "Bestbewertete Beiträge von %1",
+    "confirm": "E-Mail bestätigt",
     "maintenance.text": "%1 befindet sich derzeit in der Wartung. Bitte komme später wieder.",
     "maintenance.messageIntro": "Zusätzlich hat der Administrator diese Nachricht hinterlassen:",
     "throttled.text": "%1 ist momentan aufgrund von Überlastung nicht verfügbar. Bitte komm später wieder."
diff --git a/public/language/de/topic.json b/public/language/de/topic.json
index 3e31768859..2b34c42911 100644
--- a/public/language/de/topic.json
+++ b/public/language/de/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Bitte registrieren oder einloggen um dieses Thema zu abonnieren",
     "markAsUnreadForAll.success": "Thema für Alle als ungelesen markiert.",
     "mark_unread": "Als ungelesen markieren",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Thema als ungelesen markiert.",
     "watch": "Beobachten",
     "unwatch": "Nicht mehr beobachten",
     "watch.title": "Bei neuen Antworten benachrichtigen",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Deaktivierte Kategorien sind ausgegraut.",
     "confirm_move": "Verschieben",
     "confirm_fork": "Aufspalten",
-    "favourite": "Favorisieren",
-    "favourites": "Favoriten",
-    "favourites.has_no_favourites": "Du hast noch keine Favoriten.",
+    "favourite": "Lesezeichen",
+    "favourites": "Lesezeichen",
+    "favourites.has_no_favourites": "Du hast noch keine Beiträge als Lesezeichen gespeichert.",
     "loading_more_posts": "Lade mehr Beiträge",
     "move_topic": "Thema verschieben",
     "move_topics": "Themen verschieben",
diff --git a/public/language/de/uploads.json b/public/language/de/uploads.json
new file mode 100644
index 0000000000..2e0c183386
--- /dev/null
+++ b/public/language/de/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Lade Datei hoch...",
+    "select-file-to-upload": "Wähle eine Datei zum Hochladen aus!",
+    "upload-success": "Datei erfolgreich hochgeladen!",
+    "maximum-file-size": "Maximal %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/de/user.json b/public/language/de/user.json
index 21456e633e..e4fd8b8f8e 100644
--- a/public/language/de/user.json
+++ b/public/language/de/user.json
@@ -1,7 +1,7 @@
 {
     "banned": "Gesperrt",
     "offline": "offline",
-    "username": "Nutzername",
+    "username": "Benutzername",
     "joindate": "Registriert vor",
     "postcount": "Beiträge",
     "email": "E-Mail",
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Profilaufrufe",
     "reputation": "Ansehen",
-    "favourites": "Favoriten",
+    "favourites": "Lesezeichen",
     "watched": "Beobachtet",
     "followers": "Follower",
     "following": "Folge ich",
@@ -39,6 +39,7 @@
     "change_username": "Benutzernamen ändern",
     "change_email": "E-Mail ändern",
     "edit": "Ändern",
+    "edit-profile": "Profil ändern",
     "default_picture": "Standardsymbol",
     "uploaded_picture": "Hochgeladene Bilder",
     "upload_new_picture": "Neues Bild hochladen",
@@ -55,15 +56,16 @@
     "password": "Passwort",
     "username_taken_workaround": "Der gewünschte Benutzername ist bereits vergeben, deshalb haben wir ihn ein wenig verändert. Du bist jetzt unter dem Namen <strong>%1</strong> bekannt.",
     "password_same_as_username": "Dein Passwort entspricht deinem Benutzernamen, bitte wähle ein anderes Passwort.",
+    "password_same_as_email": "Dein Passwort entspricht deiner E-Mail-Adresse, bitte wähle ein anderes Passwort.",
     "upload_picture": "Bild hochladen",
     "upload_a_picture": "Ein Bild hochladen",
     "remove_uploaded_picture": "Hochgeladenes Bild entfernen",
-    "image_spec": "Du solltest nur PNG-, JPG- oder BMP-Dateien hochladen",
+    "upload_cover_picture": "Titelbild hochladen",
     "settings": "Einstellungen",
     "show_email": "Zeige meine E-Mail Adresse an.",
     "show_fullname": "Zeige meinen kompletten Namen an",
     "restrict_chats": "Nur Chatnachrichten von Benutzern, denen ich folge, erlauben",
-    "digest_label": "Auszug abonnieren",
+    "digest_label": "Zusammenfassung abonnieren",
     "digest_description": "Abonniere E-Mail-Benachrichtigungen für dieses Forum (neue Benachrichtigungen und Themen) nach einem festen Zeitplan.",
     "digest_off": "Aus",
     "digest_daily": "Täglich",
@@ -72,35 +74,36 @@
     "send_chat_notifications": "Sende eine E-Mail, wenn eine neue Chat-Nachricht eingeht und ich nicht online bin",
     "send_post_notifications": "Sende eine E-Mail wenn auf Themen die ich abonniert habe geantwortet wird",
     "settings-require-reload": "Einige Einstellungsänderung benötigen eine Aktualisierung. Hier klicken um die Seite neu zu laden.",
-    "has_no_follower": "Dieser User hat noch keine Follower.",
-    "follows_no_one": "Dieser User folgt noch niemandem :(",
-    "has_no_posts": "Dieser Nutzer hat noch nichts gepostet.",
-    "has_no_topics": "Dieser Nutzer hat noch keine Themen gepostet.",
-    "has_no_watched_topics": "Dieser Nutzer beobachtet keine Themen.",
+    "has_no_follower": "Dieser Benutzer hat noch keine Follower. :(",
+    "follows_no_one": "Dieser Benutzer folgt noch niemandem. :(",
+    "has_no_posts": "Dieser Benutzer hat noch nichts gepostet.",
+    "has_no_topics": "Dieser Benutzer hat noch keine Themen gepostet.",
+    "has_no_watched_topics": "Dieser Benutzer beobachtet keine Themen.",
     "has_no_upvoted_posts": "Dieser Benutzer hat bisher keine Beiträge positiv bewertet.",
     "has_no_downvoted_posts": "Dieser Benutzer hat bisher keine Beiträge negativ bewertet.",
-    "has_no_voted_posts": "Dieser Benutzer hat keine bewerteten Beiträge",
+    "has_no_voted_posts": "Dieser Benutzer hat keine bewerteten Beiträge.",
     "email_hidden": "E-Mail Adresse versteckt",
     "hidden": "versteckt",
     "paginate_description": "Themen und Beiträge in Seiten aufteilen, anstelle unendlich zu scrollen",
     "topics_per_page": "Themen pro Seite",
     "posts_per_page": "Beiträge pro Seite",
     "notification_sounds": "Ton abspielen, wenn du eine Benachrichtigung erhältst",
-    "browsing": "Stöbereinstellungen",
+    "browsing": "Browsing",
     "open_links_in_new_tab": "Ausgehende Links in neuem Tab öffnen",
     "enable_topic_searching": "Suchen innerhalb von Themen aktivieren",
     "topic_search_help": "Wenn aktiviert, ersetzt die im-Thema-Suche die Standardsuche des Browsers. Dadurch kannst du im ganzen Thema suchen, nicht nur im sichtbaren Abschnitt.",
+    "scroll_to_my_post": "Zeige eigene Antwort nach dem Erstellen im Thema an",
     "follow_topics_you_reply_to": "Themen folgen, in denen auf dich geantwortet wird",
     "follow_topics_you_create": "Themen folgen, die du erstellst",
     "grouptitle": "Wähle den anzuzeigenden Gruppen Titel aus",
     "no-group-title": "Kein Gruppentitel",
     "select-skin": "Einen Skin auswählen",
-    "select-homepage": "Eine Startseite auswählen",
+    "select-homepage": "Startseite",
     "homepage": "Startseite",
     "homepage_description": "Wähle eine Seite die als Forumstartseite benutzt werden soll aus oder 'Keine' um die Standardstartseite zu verwenden.",
     "custom_route": "Eigener Startseitenpfad",
     "custom_route_help": "Gib hier einen Pfadnamen ohne vorangehenden Slash ein (z.B. \"recent\" oder \"popular\")",
-    "sso.title": "Einmalanmeldungsdienste",
+    "sso.title": "Single Sign-on Dienste",
     "sso.associated": "Verbunden mit",
     "sso.not-associated": "Verbinde dich mit"
 }
\ No newline at end of file
diff --git a/public/language/de/users.json b/public/language/de/users.json
index 3dc0d1e78b..9a25653e0d 100644
--- a/public/language/de/users.json
+++ b/public/language/de/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Ungelesen Themen",
     "categories": "Kategorien",
     "tags": "Schlagworte",
-    "no-users-found": "No users found!"
+    "no-users-found": "Keine Benutzer gefunden!"
 }
\ No newline at end of file
diff --git a/public/language/el/error.json b/public/language/el/error.json
index b5d58d9eed..388ec06c89 100644
--- a/public/language/el/error.json
+++ b/public/language/el/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Ο Χρήστης είναι αποκλεισμένος/η",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Category does not exist",
     "no-topic": "Topic does not exist",
     "no-post": "Post does not exist",
@@ -50,8 +51,8 @@
     "still-uploading": "Παρακαλώ περίμενε να τελειώσει το ανέβασμα των αρχείων.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Δεν μπορείς να αποκλείσεις άλλους διαχειριστές!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/el/global.json b/public/language/el/global.json
index debb319dfe..d8be67df72 100644
--- a/public/language/el/global.json
+++ b/public/language/el/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Delete All",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/el/groups.json b/public/language/el/groups.json
index 93715a4f3d..29ae716a22 100644
--- a/public/language/el/groups.json
+++ b/public/language/el/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/el/modules.json b/public/language/el/modules.json
index 82c747f628..4b1dc9e48a 100644
--- a/public/language/el/modules.json
+++ b/public/language/el/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "Ο/Η %1 πληκτρολογεί...",
     "chat.user_has_messaged_you": "Ο/Η %1 σου έστειλε μήνυμα.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Παρακαλώ επέλεξε έναν παραλήπτη για να δείς το ιστορικό της συνομιλίας",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Πρόσφατες Συνομιλίες",
diff --git a/public/language/el/notifications.json b/public/language/el/notifications.json
index 1484e9b98b..53cc1e7fce 100644
--- a/public/language/el/notifications.json
+++ b/public/language/el/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "Η δημοσίευσή σου στο <strong>%2</strong> αρέσει στον/ην <strong>%1</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "Ο/Η <strong>%1</strong> επεσήμανε μια δημοσίευσή σου στο <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Το Εmail Επιβεβαιώθηκε",
     "email-confirmed-message": "Ευχαριστούμε που επιβεβαίωσες το email σου. Ο λογαριασμός σου είναι πλέον πλήρως ενεργοποιημένος.",
     "email-confirm-error-message": "Υπήρξε κάποιο πρόβλημα με την επιβεβαίωση της διεύθυνσής email σου. Ίσως ο κώδικας να είναι άκυρος ή να έχει λήξει.",
diff --git a/public/language/el/pages.json b/public/language/el/pages.json
index 18fafd820c..092f813826 100644
--- a/public/language/el/pages.json
+++ b/public/language/el/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "Το %1 αυτή την στιγμή συντηρείται. Παρακαλώ έλα αργότερα.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/el/topic.json b/public/language/el/topic.json
index 703e1e4f57..a2f1d0fcd7 100644
--- a/public/language/el/topic.json
+++ b/public/language/el/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Οι απενεργοποιημένες κατηγορίες είναι γκριζαρισμένες",
     "confirm_move": "Μετακίνηση",
     "confirm_fork": "Διαχωρισμός",
-    "favourite": "Αγαπημένο",
-    "favourites": "Αγαπημένα",
-    "favourites.has_no_favourites": "Δεν έχεις καθόλου αγαπημένα, βάλε μερικές δημοσιεύσεις στα αγαπημένα σου για να τις βλέπεις εδώ!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Φόρτωση περισσότερων δημοσιεύσεων",
     "move_topic": "Μετακίνηση Θέματος",
     "move_topics": "Μετακίνηση Θεμάτων",
diff --git a/public/language/el/uploads.json b/public/language/el/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/el/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/el/user.json b/public/language/el/user.json
index 81f2a36740..7b6eed3269 100644
--- a/public/language/el/user.json
+++ b/public/language/el/user.json
@@ -22,7 +22,7 @@
     "profile": "Προφίλ",
     "profile_views": "Views του προφίλ",
     "reputation": "Φήμη",
-    "favourites": "Αγαπημένα",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "Ακόλουθοι",
     "following": "Ακολουθά",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Επεξεργασία",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Ανεβασμένη Φωτογραφία",
     "upload_new_picture": "Ανέβασμα Νέας Φωτογραφίας",
@@ -55,10 +56,11 @@
     "password": "Κωδικός",
     "username_taken_workaround": "Το όνομα χρήστη που ζήτησες χρησιμοποιείται ήδη, οπότε το τροποποιήσαμε λίγο. Πλέον είσαι γνωστός/ή ώς <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Ανέβασμα φωτογραφίας",
     "upload_a_picture": "Ανέβασε μια φωτογραφία",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Επιλογές",
     "show_email": "Εμφάνιση του email μου",
     "show_fullname": "Show My Full Name",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/en@pirate/category.json b/public/language/en@pirate/category.json
index 031dc5efec..631d322e1a 100644
--- a/public/language/en@pirate/category.json
+++ b/public/language/en@pirate/category.json
@@ -6,11 +6,11 @@
     "no_topics": "<strong>Thar be no topics in 'tis category.</strong><br />Why don't ye give a go' postin' one?",
     "browsing": "browsin'",
     "no_replies": "No one has replied to ye message",
-    "no_new_posts": "No new posts.",
+    "no_new_posts": "Thar be no new posts.",
     "share_this_category": "Share this category",
     "watch": "Watch",
     "ignore": "Ignore",
-    "watch.message": "You are now watching updates from this category",
-    "ignore.message": "You are now ignoring updates from this category",
+    "watch.message": "Ye now be watchin' updates from 'tis category",
+    "ignore.message": "Ye now be ignorin' updates from 'tis category",
     "watched-categories": "Watched categories"
 }
\ No newline at end of file
diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json
index ca4533474d..0709e823b6 100644
--- a/public/language/en@pirate/error.json
+++ b/public/language/en@pirate/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "User banned",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Category does not exist",
     "no-topic": "Topic does not exist",
     "no-post": "Post does not exist",
@@ -50,8 +51,8 @@
     "still-uploading": "Please wait for uploads to complete.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "You can't ban other admins!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/en@pirate/global.json b/public/language/en@pirate/global.json
index 8b7fa92380..58eba7f42d 100644
--- a/public/language/en@pirate/global.json
+++ b/public/language/en@pirate/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Delete All",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/en@pirate/groups.json b/public/language/en@pirate/groups.json
index 2ac271a4c5..3c4f6ce638 100644
--- a/public/language/en@pirate/groups.json
+++ b/public/language/en@pirate/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/en@pirate/modules.json b/public/language/en@pirate/modules.json
index ceb094e5a2..0ae551df59 100644
--- a/public/language/en@pirate/modules.json
+++ b/public/language/en@pirate/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 is typing ...",
     "chat.user_has_messaged_you": "%1 has messaged you.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Please select a recipient to view chat message history",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Recent Chats",
diff --git a/public/language/en@pirate/notifications.json b/public/language/en@pirate/notifications.json
index 526e8f9a6d..cee3aa994b 100644
--- a/public/language/en@pirate/notifications.json
+++ b/public/language/en@pirate/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Confirmed",
     "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
     "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
diff --git a/public/language/en@pirate/pages.json b/public/language/en@pirate/pages.json
index bcae168282..285aa30017 100644
--- a/public/language/en@pirate/pages.json
+++ b/public/language/en@pirate/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/en@pirate/topic.json b/public/language/en@pirate/topic.json
index f78f1d49bb..c7ce76e07b 100644
--- a/public/language/en@pirate/topic.json
+++ b/public/language/en@pirate/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Disabled Categories are greyed out",
     "confirm_move": "Move",
     "confirm_fork": "Fork",
-    "favourite": "Favourite",
-    "favourites": "Favourites",
-    "favourites.has_no_favourites": "You don't have any favourites, favourite some posts to see them here!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Loading More Posts",
     "move_topic": "Move Topic",
     "move_topics": "Move Topics",
diff --git a/public/language/en@pirate/uploads.json b/public/language/en@pirate/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/en@pirate/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/en@pirate/user.json b/public/language/en@pirate/user.json
index 33e7c3e222..0b5959462f 100644
--- a/public/language/en@pirate/user.json
+++ b/public/language/en@pirate/user.json
@@ -22,7 +22,7 @@
     "profile": "Profile",
     "profile_views": "Profile views",
     "reputation": "Reputation",
-    "favourites": "Favourites",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "Followers",
     "following": "Following",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Edit",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Uploaded Picture",
     "upload_new_picture": "Upload New Picture",
@@ -55,10 +56,11 @@
     "password": "Password",
     "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Upload picture",
     "upload_a_picture": "Upload a picture",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Settings",
     "show_email": "Show My Email",
     "show_fullname": "Show My Full Name",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/en_GB/email.json b/public/language/en_GB/email.json
index 012270d2ab..e893709772 100644
--- a/public/language/en_GB/email.json
+++ b/public/language/en_GB/email.json
@@ -31,6 +31,7 @@
 	"digest.day": "day",
 	"digest.week": "week",
 	"digest.month": "month",
+	"digest.subject": "Digest for %1",
 
 	"notif.chat.subject": "New chat message received from %1",
 	"notif.chat.cta": "Click here to continue the conversation",
diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json
index 2921a55a35..e9a4cbea7d 100644
--- a/public/language/en_GB/error.json
+++ b/public/language/en_GB/error.json
@@ -34,6 +34,7 @@
 
 	"user-banned": "User banned",
 	"user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+	"blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
 
 	"no-category": "Category does not exist",
 	"no-topic": "Topic does not exist",
@@ -125,5 +126,6 @@
 
 	"no-session-found": "No login session found!",
 	"not-in-room": "User not in room",
-	"no-users-in-room": "No users in this room"
+	"no-users-in-room": "No users in this room",
+	"cant-kick-self": "You can't kick yourself from the group"
 }
diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json
index 1b2cea5bd4..a7959aa349 100644
--- a/public/language/en_GB/global.json
+++ b/public/language/en_GB/global.json
@@ -111,5 +111,8 @@
 	"map": "Map",
 	"sessions": "Login Sessions",
 	"ip_address": "IP Address",
-	"enter_page_number": "Enter page number"
+	"enter_page_number": "Enter page number",
+	"upload_file": "Upload file",
+	"upload": "Upload",
+	"allowed-file-types": "Allowed file types are %1"
 }
diff --git a/public/language/en_GB/groups.json b/public/language/en_GB/groups.json
index 08982e8d29..8d129fe376 100644
--- a/public/language/en_GB/groups.json
+++ b/public/language/en_GB/groups.json
@@ -47,6 +47,7 @@
 	"details.hidden": "Hidden",
 	"details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
 	"details.delete_group": "Delete Group",
+	"details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
 
 	"event.updated": "Group details have been updated",
 	"event.deleted": "The group \"%1\" has been deleted",
@@ -57,5 +58,6 @@
 	"membership.leave-group": "Leave Group",
 	"membership.reject": "Reject",
 
-	"new-group.group_name": "Group Name:"
+	"new-group.group_name": "Group Name:",
+	"upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/en_GB/modules.json b/public/language/en_GB/modules.json
index a2459cbc8c..a3db35f6e3 100644
--- a/public/language/en_GB/modules.json
+++ b/public/language/en_GB/modules.json
@@ -6,6 +6,7 @@
 	"chat.user_typing": "%1 is typing ...",
 	"chat.user_has_messaged_you": "%1 has messaged you.",
 	"chat.see_all": "See all chats",
+	"chat.mark_all_read": "Mark all chats read",
 	"chat.no-messages": "Please select a recipient to view chat message history",
 	"chat.no-users-in-room": "No users in this room",
 	"chat.recent-chats": "Recent Chats",
diff --git a/public/language/en_GB/notifications.json b/public/language/en_GB/notifications.json
index a02cada4a6..07ec757374 100644
--- a/public/language/en_GB/notifications.json
+++ b/public/language/en_GB/notifications.json
@@ -32,6 +32,7 @@
 	"user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
 	"user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
 	"new_register": "<strong>%1</strong> sent a registration request.",
+	"new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
 
 	"email-confirmed": "Email Confirmed",
 	"email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index e99e90b96c..6299b8aac1 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -30,7 +30,7 @@
 	"flag": "Flag",
 	"locked": "Locked",
 
-	"bookmark_instructions" : "Click here to return to the last unread post in this thread.",
+	"bookmark_instructions" : "Click here to return to the last read post in this thread.",
 
 	"flag_title": "Flag this post for moderation",
 	"flag_success": "This post has been flagged for moderation.",
diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json
index 3351e8aa23..07c85aa19f 100644
--- a/public/language/en_GB/user.json
+++ b/public/language/en_GB/user.json
@@ -42,6 +42,7 @@
 	"change_username": "Change Username",
 	"change_email": "Change Email",
 	"edit": "Edit",
+	"edit-profile": "Edit Profile",
 	"default_picture": "Default Icon",
 	"uploaded_picture": "Uploaded Picture",
 	"upload_new_picture": "Upload New Picture",
@@ -63,7 +64,7 @@
 	"upload_picture": "Upload picture",
 	"upload_a_picture": "Upload a picture",
 	"remove_uploaded_picture" : "Remove Uploaded Picture",
-	"image_spec": "You may only upload PNG, JPG, or BMP files",
+	"upload_cover_picture": "Upload cover picture",
 
 	"settings": "Settings",
 	"show_email": "Show My Email",
@@ -103,6 +104,8 @@
 	"enable_topic_searching": "Enable In-Topic Searching",
 	"topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
 
+	"scroll_to_my_post": "After posting a reply, show the new post",
+
 	"follow_topics_you_reply_to": "Follow topics that you reply to",
 	"follow_topics_you_create": "Follow topics you create",
 
diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json
index f75bdad526..0709e823b6 100644
--- a/public/language/en_US/error.json
+++ b/public/language/en_US/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "User banned",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Category does not exist",
     "no-topic": "Topic does not exist",
     "no-post": "Post does not exist",
@@ -50,8 +51,8 @@
     "still-uploading": "Please wait for uploads to complete.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favorited this post",
-    "already-unfavourited": "You have already unfavorited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "You can't ban other admins!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/en_US/global.json b/public/language/en_US/global.json
index 7579fecc9e..a206afd7cf 100644
--- a/public/language/en_US/global.json
+++ b/public/language/en_US/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Delete All",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/en_US/groups.json b/public/language/en_US/groups.json
index ce19a2e4b7..ffc0224c5e 100644
--- a/public/language/en_US/groups.json
+++ b/public/language/en_US/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/en_US/modules.json b/public/language/en_US/modules.json
index 474b5fc7e2..eb5e513640 100644
--- a/public/language/en_US/modules.json
+++ b/public/language/en_US/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 is typing ...",
     "chat.user_has_messaged_you": "%1 has messaged you.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Please select a recipient to view chat message history",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Recent Chats",
diff --git a/public/language/en_US/notifications.json b/public/language/en_US/notifications.json
index 65b6dcd24b..28d35e65fa 100644
--- a/public/language/en_US/notifications.json
+++ b/public/language/en_US/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favorited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Confirmed",
     "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
     "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
diff --git a/public/language/en_US/pages.json b/public/language/en_US/pages.json
index 48d10023a2..285aa30017 100644
--- a/public/language/en_US/pages.json
+++ b/public/language/en_US/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favorite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/en_US/topic.json b/public/language/en_US/topic.json
index b4179826fb..bdf6d77f91 100644
--- a/public/language/en_US/topic.json
+++ b/public/language/en_US/topic.json
@@ -26,7 +26,7 @@
     "tools": "Tools",
     "flag": "Flag",
     "locked": "Locked",
-    "bookmark_instructions": "Click here to return to the last unread post in this thread.",
+    "bookmark_instructions": "Click here to return to the last read post in this thread.",
     "flag_title": "Flag this post for moderation",
     "flag_success": "This post has been flagged for moderation.",
     "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Disabled Categories are greyed out",
     "confirm_move": "Move",
     "confirm_fork": "Fork",
-    "favourite": "Favorite",
-    "favourites": "Favorites",
-    "favourites.has_no_favourites": "You don't have any favorites, favorite some posts to see them here!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Loading More Posts",
     "move_topic": "Move Topic",
     "move_topics": "Move Topics",
diff --git a/public/language/en_US/uploads.json b/public/language/en_US/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/en_US/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/en_US/user.json b/public/language/en_US/user.json
index 88356e9ce8..86d31e818d 100644
--- a/public/language/en_US/user.json
+++ b/public/language/en_US/user.json
@@ -22,7 +22,7 @@
     "profile": "Profile",
     "profile_views": "Profile views",
     "reputation": "Reputation",
-    "favourites": "Favorites",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "Followers",
     "following": "Following",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Edit",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Uploaded Picture",
     "upload_new_picture": "Upload New Picture",
@@ -55,10 +56,11 @@
     "password": "Password",
     "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Upload picture",
     "upload_a_picture": "Upload a picture",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Settings",
     "show_email": "Show My Email",
     "show_fullname": "Show My Full Name",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behavior and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/es/error.json b/public/language/es/error.json
index 9251615b8d..630475a16f 100644
--- a/public/language/es/error.json
+++ b/public/language/es/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Contraseña no válida",
     "invalid-username-or-password": "Por favor especifica tanto un usuario como contraseña",
     "invalid-search-term": "Término de búsqueda inválido",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Número de página inválido, debe estar entre %1 y %2",
     "username-taken": "Nombre de usuario ocupado",
     "email-taken": "Correo electrónico ocupado",
     "email-not-confirmed": "Su cuenta de correo electrónico no ha sido confirmada aún, por favor haga click aquí para confirmarla.",
@@ -27,6 +27,7 @@
     "password-too-long": "Contraseña muy corta",
     "user-banned": "Usuario baneado",
     "user-too-new": "Lo sentimos, es necesario que esperes %1 segundo(s) antes poder hacer tu primera publicación",
+    "blacklisted-ip": "Lo sentimos, tu dirección IP ha sido baneada de esta comunidad. Si crees que debe de haber un error, por favor contacte con un administrador.",
     "no-category": "La categoría no existe",
     "no-topic": "El tema no existe",
     "no-post": "La publicación no existe",
@@ -50,8 +51,8 @@
     "still-uploading": "Por favor, espera a que terminen las subidas.",
     "file-too-big": "El tamaño de fichero máximo es de %1 kB - por favor, suba un fichero más pequeño",
     "guest-upload-disabled": "Las subidas están deshabilitadas para los invitados",
-    "already-favourited": "Ya ha marcado esta publicación como favorita",
-    "already-unfavourited": "Ya ha desmarcado esta publicación como favorita",
+    "already-favourited": "Ya habías guardado este post.",
+    "already-unfavourited": "Ya habías desguardado este post.",
     "cant-ban-other-admins": "¡No puedes expulsar a otros administradores!",
     "cant-remove-last-admin": "Tu eres el unico administrador. Añade otro usuario como administrador antes de eliminarte a ti mismo.",
     "invalid-image-type": "Tipo de imagen inválido. Los tipos permitidos son: %1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Mensaje de Chat es demasiado largo",
     "cant-edit-chat-message": "No tienes permiso para editar este mensaje",
     "cant-remove-last-user": "No puedes eliminar el último usuario",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "No tienes permiso para eliminar este mensaje",
     "reputation-system-disabled": "El sistema de reputación está deshabilitado.",
     "downvoting-disabled": "La votación negativa está deshabilitada.",
     "not-enough-reputation-to-downvote": "No tienes suficiente reputación para votar negativo este post",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Por favor introduce tu nombre de usuario para acceder",
     "invite-maximum-met": "Has alcanzado el número máximo de personas invitadas (%1 de %2).",
     "no-session-found": "¡No se ha encontrado ningún inicio de sesión!",
-    "not-in-room": "User not in room"
+    "not-in-room": "El usuario no está en la sala",
+    "no-users-in-room": "No hay usuarios en esta sala",
+    "cant-kick-self": "No te puedes expulsar a ti mismo del grupo"
 }
\ No newline at end of file
diff --git a/public/language/es/global.json b/public/language/es/global.json
index 83c24ff95e..d81c10ca88 100644
--- a/public/language/es/global.json
+++ b/public/language/es/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "publicado en %1 %2 por %3",
     "user_posted_ago": "%1 publicó %2",
     "guest_posted_ago": "Invitado publicó %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "Última edición por %1",
     "norecentposts": "No hay publicaciones recientes",
     "norecenttopics": "No hay temas recientes",
     "recentposts": "Publicaciones recientes",
@@ -86,5 +86,9 @@
     "delete_all": "Eliminar todo",
     "map": "Mapa",
     "sessions": "Inicios de sesión",
-    "ip_address": "Direcciones IP"
+    "ip_address": "Direcciones IP",
+    "enter_page_number": "Escribe el número de página",
+    "upload_file": "Subir archivo",
+    "upload": "Subir",
+    "allowed-file-types": "Los tipos de archivos permitidos son: %1"
 }
\ No newline at end of file
diff --git a/public/language/es/groups.json b/public/language/es/groups.json
index 28b541e573..657fe67599 100644
--- a/public/language/es/groups.json
+++ b/public/language/es/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Oculto",
     "details.hidden_help": "Si está habilitado, este grupo no aparecerá en los listados de grupos, y los usuarios tendrán que ser invitados manualmente",
     "details.delete_group": "Eliminar grupo",
+    "details.private_system_help": "Los grupos privados están desactivados a nivel de sistema, esta opción no cambiará nada.",
     "event.updated": "Los detalles del grupo han sido actualizados",
     "event.deleted": "El grupo \"%1\" ha sido eliminado",
     "membership.accept-invitation": "Aceptar Invitación",
@@ -48,5 +49,6 @@
     "membership.join-group": "Unirse al grupo",
     "membership.leave-group": "Dejar el grupo",
     "membership.reject": "Rechazar",
-    "new-group.group_name": "Nombre de Grupo:"
+    "new-group.group_name": "Nombre de Grupo:",
+    "upload-group-cover": "Cargar foto para el grupo"
 }
\ No newline at end of file
diff --git a/public/language/es/modules.json b/public/language/es/modules.json
index 917d284bd9..f0e34b04a1 100644
--- a/public/language/es/modules.json
+++ b/public/language/es/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 está escribiendo...",
     "chat.user_has_messaged_you": "%1 te ha enviado un mensaje.",
     "chat.see_all": "Ver todos los chats",
+    "chat.mark_all_read": "Marcar todos los chats como leídos",
     "chat.no-messages": "Por favor, selecciona un contacto para ver el historial de mensajes",
     "chat.no-users-in-room": "No hay usuarios en esta sala",
     "chat.recent-chats": "Chats recientes",
diff --git a/public/language/es/notifications.json b/public/language/es/notifications.json
index 96390cb0e5..1effb38fee 100644
--- a/public/language/es/notifications.json
+++ b/public/language/es/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> y otras %2 personas han votado positivamente tu respuesta en <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> su tema ha sido movido a <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> se ha movido <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> ha marcado como favorito su publicación en <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> y <strong>%2</strong> han marcado como favorito tu post en <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> y otras %2 personas han marcado como favorito tu post en <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> se ha guardado su post en <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> y <strong>%2</strong> se han guardado su post en <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> y otros %2 usuarios se han guardado su post en <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> ha reportado una respuesta en <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> y <strong>%2</strong> han reportado un post en <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> y otras %2 personas han reportado un post en <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> y <strong>%2</strong> comenzaron a seguirte.",
     "user_started_following_you_multiple": "<strong>%1</strong> y otras %2 personas comenzaron a seguirte.",
     "new_register": "<strong>%1</strong> envió una solicitud de registro.",
+    "new_register_multiple": "Hay <strong>%1</strong> peticiones de registros pendientes de revisión",
     "email-confirmed": "Correo electrónico confirmado",
     "email-confirmed-message": "Gracias por validar tu correo electrónico. Tu cuenta ya está completamente activa.",
     "email-confirm-error-message": "Hubo un problema al validar tu cuenta de correo electrónico. Quizá el código era erróneo o expiró...",
diff --git a/public/language/es/pages.json b/public/language/es/pages.json
index a4d13047d1..4a79f28222 100644
--- a/public/language/es/pages.json
+++ b/public/language/es/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Temas populares del mes",
     "popular-alltime": "Temas populares de siempre",
     "recent": "Temas recientes",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Posts reportados",
     "users/online": "Conectados",
     "users/latest": "Últimos usuarios",
     "users/sort-posts": "Top por mensajes",
     "users/sort-reputation": "Más reputados",
-    "users/banned": "Banned Users",
+    "users/banned": "Usuarios baneados",
     "users/search": "Buscar",
     "notifications": "Notificaciones",
     "tags": "Etiquetas",
@@ -33,12 +33,13 @@
     "account/posts": "Publicados por %1",
     "account/topics": "Temas creados por %1",
     "account/groups": "Grupos de %1",
-    "account/favourites": "Favoritos de %1",
+    "account/favourites": "Publicaciones favoritas de %1 ",
     "account/settings": "Preferencias",
     "account/watched": "Temas seguidos por %1",
     "account/upvoted": "Publicaciones votadas positivamente %1",
     "account/downvoted": "Publicaciones votadas negativamente %1",
     "account/best": "Mejores publicaciones hechas por %1",
+    "confirm": "Correo electrónico confirmado",
     "maintenance.text": "%1 está en mantenimiento actualmente. Por favor vuelva en otro momento.",
     "maintenance.messageIntro": "Además, la administración ha dejado este mensaje:",
     "throttled.text": "%1 no está disponible debido a una carga excesiva. Por favor vuelva en otro momento"
diff --git a/public/language/es/topic.json b/public/language/es/topic.json
index 927570f32b..1f13a9e8e9 100644
--- a/public/language/es/topic.json
+++ b/public/language/es/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Las categorías deshabilitadas están en gris",
     "confirm_move": "Mover",
     "confirm_fork": "Dividir",
-    "favourite": "Favorito",
-    "favourites": "Favoritos",
-    "favourites.has_no_favourites": "No tienes favoritos, ¡puedes agregar alguno y volver a verlos aquí!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Cargando más publicaciones",
     "move_topic": "Mover tema",
     "move_topics": "Mover temas",
diff --git a/public/language/es/uploads.json b/public/language/es/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/es/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/es/user.json b/public/language/es/user.json
index 77f0084c52..b518f7c38c 100644
--- a/public/language/es/user.json
+++ b/public/language/es/user.json
@@ -22,7 +22,7 @@
     "profile": "Perfil",
     "profile_views": "Visitas",
     "reputation": "Reputación",
-    "favourites": "Favoritos",
+    "favourites": "Bookmarks",
     "watched": "Suscritos",
     "followers": "Seguidores",
     "following": "Siguiendo",
@@ -39,6 +39,7 @@
     "change_username": "Cambiar nombre de usuario",
     "change_email": "Cambiar email",
     "edit": "Editar",
+    "edit-profile": "Edit Profile",
     "default_picture": "Icono por defecto",
     "uploaded_picture": "Imagen subida",
     "upload_new_picture": "Subir nueva imagen",
@@ -55,10 +56,11 @@
     "password": "Contraseña",
     "username_taken_workaround": "El nombre de usuario que has solicitada ya está siendo usado, por tanto lo hemos alterado ligeramente. Ahora eres conocido como <strong>%1</strong>.",
     "password_same_as_username": "Tu Constraseña es igual al nombre de Usuario, por favor seleccione otra Constraseña.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Subir foto",
     "upload_a_picture": "Subir una foto",
     "remove_uploaded_picture": "Borrar Imagen subida",
-    "image_spec": "Sólo puedes subir imágenes en formato PNG, JPG o BMP",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Opciones",
     "show_email": "Mostrar mi correo electrónico",
     "show_fullname": "Mostrar mi nombre completo",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Abrir los enlaces externos en una nueva pestaña",
     "enable_topic_searching": "Activar la búsqueda \"in-topic\"",
     "topic_search_help": "Si está activada, la búsqueda 'in-topic' sustituirá el comportamiento por defecto del navegador y le permitirá buscar en el tema al completo, en vez de hacer una búsqueda únicamente sobre el contenido mostrado en pantalla",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Seguir los temas en los que respondes",
     "follow_topics_you_create": "Seguir publicaciones que creas",
     "grouptitle": "Selecciona el título del grupo que deseas visualizar",
diff --git a/public/language/et/error.json b/public/language/et/error.json
index 5520d6f965..013db49d04 100644
--- a/public/language/et/error.json
+++ b/public/language/et/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Vigane parool",
     "invalid-username-or-password": "Palun täpsusta kasutajanime ja parooli",
     "invalid-search-term": "Vigane otsingusõna",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Väär lehekülje numeratsioon, peab olema vähemalt %1 ja kõige rohkem %2",
     "username-taken": "Kasutajanimi on juba võetud",
     "email-taken": "Email on võetud",
     "email-not-confirmed": "Su emaili aadress ei ole kinnitatud, vajuta siia et kinnitada.",
@@ -27,6 +27,7 @@
     "password-too-long": "Parool liiga pikk",
     "user-banned": "Kasutaja bannitud",
     "user-too-new": "Vabandust, te peate ootama %1 sekund(it) enne esimese postituse loomist.",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategooriat ei eksisteeri",
     "no-topic": "Teemat ei eksisteeri",
     "no-post": "Postitust ei eksisteeri",
@@ -50,8 +51,8 @@
     "still-uploading": "Palun oota, kuni üleslaadimised on laetud.",
     "file-too-big": "Maksimaalne üleslaetava faili suurus on %1 kB - valige väiksema mahuga fail.",
     "guest-upload-disabled": "Külaliste üleslaadimine on keelatud.",
-    "already-favourited": "Sa juba märkisid selle postituse lemmikuks",
-    "already-unfavourited": "Sa juba eemaldasid selle postituse lemmikute hulgast",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Sa ei saa bannida teisi administraatoreid!",
     "cant-remove-last-admin": "Te olete ainus administraator. Lisage keegi teine administraatoriks, enne kui eemaldate endalt administraatori.",
     "invalid-image-type": "Vigane pildi formaat. Lubatud formaadid on: %1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Vestluse sõnum on liiga pikk",
     "cant-edit-chat-message": "Sul ei ole lubatud antud sõnumit muuta",
     "cant-remove-last-user": "Sa ei saa viimast kasutajat eemaldada",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Sul ei ole lubatud antud sõnumit kustutada",
     "reputation-system-disabled": "Reputatsiooni süsteem ei ole aktiveeritud",
     "downvoting-disabled": "Negatiivsete häälte andmine ei ole võimaldatud",
     "not-enough-reputation-to-downvote": "Sul ei ole piisavalt reputatsiooni, et anda negatiivset hinnangut sellele postitusele.",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Sisse logimiseks kasuta oma kasutajanime",
     "invite-maximum-met": "Sa oled kutsunud maksimaalse lubatud inimeste arvu (%1 %2 'st).",
     "no-session-found": "Sisse logimis sessiooni ei leitud!",
-    "not-in-room": "User not in room"
+    "not-in-room": "Kasutaja pole ruumis",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/et/global.json b/public/language/et/global.json
index 608274ff50..f43dd9007a 100644
--- a/public/language/et/global.json
+++ b/public/language/et/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "%3 postitas %2 kategooriasse %1",
     "user_posted_ago": "%1 postitas %2",
     "guest_posted_ago": "Külaline postitas %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "viimati muudetud %1 poolt",
     "norecentposts": "Hiljutisi postitusi ei ole",
     "norecenttopics": "Hiljutisi teemasid ei ole",
     "recentposts": "Hiljutised postitused",
@@ -86,5 +86,9 @@
     "delete_all": "Kustuta kõik",
     "map": "Kaart",
     "sessions": "Logitud Sessioonid",
-    "ip_address": "IP Aadress"
+    "ip_address": "IP Aadress",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/et/groups.json b/public/language/et/groups.json
index 786ff6489a..db8dfce3d1 100644
--- a/public/language/et/groups.json
+++ b/public/language/et/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Peidetud",
     "details.hidden_help": "Kui sisse lülitatud, siis seda gruppi ei kuvata gruppide nimekirjas ning liikmed tuleb lisada manuaalselt",
     "details.delete_group": "Kustuta grupp",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Grupi lisainformatsiooni on uuendatud",
     "event.deleted": "Grupp \"%1\" on kustutatud",
     "membership.accept-invitation": "Võta kutse vastu",
@@ -48,5 +49,6 @@
     "membership.join-group": "Liitu grupiga",
     "membership.leave-group": "Lahku grupist",
     "membership.reject": "Lükka tagasi",
-    "new-group.group_name": "Grupi nimi:"
+    "new-group.group_name": "Grupi nimi:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/et/modules.json b/public/language/et/modules.json
index 10749826f7..ec23804489 100644
--- a/public/language/et/modules.json
+++ b/public/language/et/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 kirjutab sõnumit...",
     "chat.user_has_messaged_you": "%1 saatis sulle sõnumi.",
     "chat.see_all": "Vaata kõiki vestluseid",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Vali sõnumisaaja, et vaadata sõnumite ajalugu.",
     "chat.no-users-in-room": "Ühtki kasutajat selles ruumis",
     "chat.recent-chats": "Hiljutised vestlused",
diff --git a/public/language/et/notifications.json b/public/language/et/notifications.json
index 1dc8c61a6b..11e2823a75 100644
--- a/public/language/et/notifications.json
+++ b/public/language/et/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> ja %2 teist on kiitnud sinu postituse heaks: <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> liigutas sinu postituse <strong>%2 'sse</strong>",
     "moved_your_topic": "<strong>%1</strong> liigutas <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> märgistas sinu postituse lemmikuks teemas <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1 'le</strong> ja <strong>%2 'le</strong> meeldis sinu postitus: <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1 'le</strong> ja %2 'le teisele meeldis sinu postitus: <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> raporteeris postitust <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> ja <strong>%2</strong> märgistasid postituse: <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> ja %2 teist märgistasid postituse: <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> ja <strong>%2</strong> hakkasid sind jälgima.",
     "user_started_following_you_multiple": "<strong>%1</strong> ja %2 hakkasid sind jälgima.",
     "new_register": "<strong>%1</strong> saatis registreerimistaotluse.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Emaili aadress kinnitatud",
     "email-confirmed-message": "Täname, et kinnitasite oma emaili aadressi. Teie kasutaja on nüüd täielikult aktiveeritud.",
     "email-confirm-error-message": "Emaili aadressi kinnitamisel tekkis viga. Võibolla kinnituskood oli vale või aegunud.",
diff --git a/public/language/et/pages.json b/public/language/et/pages.json
index 6c2d826b95..f85b28ead0 100644
--- a/public/language/et/pages.json
+++ b/public/language/et/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Populaarsed teemad sel kuul",
     "popular-alltime": "Populaarseimad teemad üldse",
     "recent": "Hiljutised teemad",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Märgistatud postitused",
     "users/online": "Sisseloginud kasutajad",
     "users/latest": "Hiljutised kasutajad",
     "users/sort-posts": "Kasutajad, kel on enim postitusi",
     "users/sort-reputation": "Suurima reputatsiooniga kasutajad",
-    "users/banned": "Banned Users",
+    "users/banned": "Keelustatud Kasutajad",
     "users/search": "Kasutajate otsing",
     "notifications": "Teated",
     "tags": "Märksõnad",
@@ -33,12 +33,13 @@
     "account/posts": "Postitused, mis on tehtud kasutaja %1 poolt",
     "account/topics": "Teemad on kirjutanud %1",
     "account/groups": "Kasutaja %1 grupid",
-    "account/favourites": "Kasutaja %1 lemmikud postitused",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Kasutaja sätted",
     "account/watched": "Teemasid jälgib %1 kasutajat",
     "account/upvoted": "Postitused %1 poolt heaks kiidetud",
     "account/downvoted": "Postitused %1 poolt vastu hääletatud",
     "account/best": "Parimad postitused %1 poolt",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 foorumil on käimas hooldustööd. Palun külastage meid mõne aja pärast uuesti.",
     "maintenance.messageIntro": "Administraator on jätnud ka omaltpoolt sõnumi:",
     "throttled.text": "%1 ei ole hetkel kättesaadav liigse koormuse tõttu. Palun tulge tagasi mõni teine kord."
diff --git a/public/language/et/topic.json b/public/language/et/topic.json
index 880709a196..d6aba7fb7a 100644
--- a/public/language/et/topic.json
+++ b/public/language/et/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Palun registreeru kasutajaks või logi sisse, et tellida teateid selle postituse kohta.",
     "markAsUnreadForAll.success": "Teema märgitud mitte-loetuks kõikidele.",
     "mark_unread": "Märgi lugematuks",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Teema märgitud mitteloetuks.",
     "watch": "Vaata",
     "unwatch": "Ära järgi",
     "watch.title": "Saa teateid uutest postitustest siin teemas",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Kinnised kategooriad on hallid",
     "confirm_move": "Liiguta",
     "confirm_fork": "Fork",
-    "favourite": "Märgi lemmikuks",
-    "favourites": "Lemmikud ",
-    "favourites.has_no_favourites": "Sul pole lemmikuid postitusi. Märgi mõned postitused lemmikuks ning need ilmuvad automaatselt siia!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Laen postitusi",
     "move_topic": "Liiguta teemat",
     "move_topics": "Liiguta teemasi",
diff --git a/public/language/et/uploads.json b/public/language/et/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/et/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/et/user.json b/public/language/et/user.json
index 45af939288..463b4c5e8e 100644
--- a/public/language/et/user.json
+++ b/public/language/et/user.json
@@ -22,7 +22,7 @@
     "profile": "Profiil",
     "profile_views": "Vaatamisi",
     "reputation": "Reputatsioon",
-    "favourites": "Lemmikud",
+    "favourites": "Bookmarks",
     "watched": "Vaadatud",
     "followers": "Jälgijad",
     "following": "Jälgimised",
@@ -39,6 +39,7 @@
     "change_username": "Vaheta kasutajanime",
     "change_email": "Vaheta emaili",
     "edit": "Muuda",
+    "edit-profile": "Edit Profile",
     "default_picture": "Algne ikoon",
     "uploaded_picture": "Üleslaetud pilt",
     "upload_new_picture": "Laadi uus pilt",
@@ -55,10 +56,11 @@
     "password": "Parool",
     "username_taken_workaround": "Kasutajanimi mida soovisid, ei olnud saadaval, seeg muutsime seda natukene. Sinu uus kasutajanimi on nüüd: <strong>%1</strong>",
     "password_same_as_username": "Su parool kattub su kasutajanimega, palun vali mõni muu parool.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Laadi pilt",
     "upload_a_picture": "Lae pilt üles",
     "remove_uploaded_picture": "Eemalda üleslaetud pilt",
-    "image_spec": "Sa saad üles laadida ainult PNG, JPG või BMP vormingus faile.",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Seaded",
     "show_email": "Näita minu emaili",
     "show_fullname": "Näita minu täisnime",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Ava väljaminevad lingid uues aknas",
     "enable_topic_searching": "Võimalda teemasisene otsing",
     "topic_search_help": "Kui see on sisse lükatud, siis teemasisene otsing võtab üle brauseri tavapärase otsingu ning võimaldab otsida ainult ekraanile mahtuva teema asemel terve teema ulatuses.",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Järgi teemasid, millele olete vastanud.",
     "follow_topics_you_create": "Järgi teemasi, mis on teie loodud.",
     "grouptitle": "Vali grupile tiitel mida kuvada soovid",
diff --git a/public/language/et/users.json b/public/language/et/users.json
index a1dd6ba266..bb377c7239 100644
--- a/public/language/et/users.json
+++ b/public/language/et/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Lugemata teemad",
     "categories": "Kategooriad",
     "tags": "Märksõnad",
-    "no-users-found": "No users found!"
+    "no-users-found": "Ühtki kasutajat ei leitud!"
 }
\ No newline at end of file
diff --git a/public/language/fa_IR/email.json b/public/language/fa_IR/email.json
index a4216ef881..1ec13b4a26 100644
--- a/public/language/fa_IR/email.json
+++ b/public/language/fa_IR/email.json
@@ -21,9 +21,9 @@
     "digest.cta": "برای دیدن %1 اینجا کلیک کنید",
     "digest.unsub.info": "این اعداد که برای شما فرستاده شده به علت تنظیمات اشترک شماست.",
     "digest.no_topics": "در %1 گذشته هیچ موضوعی فعال نبوده است",
-    "digest.day": "day",
-    "digest.week": "week",
-    "digest.month": "month",
+    "digest.day": "روز",
+    "digest.week": "هفته",
+    "digest.month": "ماه",
     "notif.chat.subject": "پیام چتی جدیدی از %1 دریافت شد",
     "notif.chat.cta": "برای ادامه‌ی چت اینجا کلیک کنید",
     "notif.chat.unsub.info": "این اطلاعیه ی چتیی که برای شما فرستاده شده به علت تنظیمات اشترک شماست.",
diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json
index d1ff0894f9..c6651bc9d8 100644
--- a/public/language/fa_IR/error.json
+++ b/public/language/fa_IR/error.json
@@ -24,9 +24,10 @@
     "confirm-email-already-sent": "ایمیل فعال‌سازی قبلا فرستاده شده، لطفا %1 دقیقه صبر کنید تا ایمیل دیگری بفرستید.",
     "username-too-short": "نام کاربری خیلی کوتاه است.",
     "username-too-long": "نام کاربری بسیار طولانیست",
-    "password-too-long": "Password too long",
+    "password-too-long": "کلمه عبور بسیار طولانیست",
     "user-banned": "کاربر محروم شد.",
     "user-too-new": "با عرض پوزش، شما باید %1 ثانیه پیش از فرستادن پست نخست خود صبر کنید",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "دسته بندی وجود ندارد",
     "no-topic": "موضوع وجود ندارد.",
     "no-post": "پست وجود ندارد",
@@ -50,8 +51,8 @@
     "still-uploading": "خواهشمندیم تا پایان بارگذاری‌ها شکیبا باشید.",
     "file-too-big": "حداکثر مجاز حجم فایل %1 کیلوبایت می باشد - لطفا فایلی با حجم کمتر بارگذاری کنید",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "شما قبلا این پست را محبوب کرده اید",
-    "already-unfavourited": "شما قبلا این پست را نامحبوب کرده اید",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "شما نمی‌توانید دیگر مدیران را محروم کنید!",
     "cant-remove-last-admin": "شما تنها مدیر می باشید . شما باید قبل از عزل خود از مدیریت یک کاربر دیگر را مدیر کنید",
     "invalid-image-type": "نوع تصویر نامعتبر است. نوعهای قابل قبول اینها هستند: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "لطفا از نام کاربری خود برای ورود استفاده کنید",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "هیچ کاربری در این گفتگو نیست",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/fa_IR/global.json b/public/language/fa_IR/global.json
index a11d36a14e..948145c589 100644
--- a/public/language/fa_IR/global.json
+++ b/public/language/fa_IR/global.json
@@ -86,5 +86,9 @@
     "delete_all": "حذف همه",
     "map": "نقشه",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/fa_IR/groups.json b/public/language/fa_IR/groups.json
index c3658c3425..2c1db5b30d 100644
--- a/public/language/fa_IR/groups.json
+++ b/public/language/fa_IR/groups.json
@@ -24,7 +24,7 @@
     "details.has_no_posts": "اعضای این گروه هیچ پستی ایجاد نکرده اند",
     "details.latest_posts": "آخرین پست ها",
     "details.private": "خصوصی",
-    "details.disableJoinRequests": "Disable join requests",
+    "details.disableJoinRequests": "غیر فعال کردن درخواستهای عضویت",
     "details.grant": "اعطاء/خلع مالکیت",
     "details.kick": "بیرون انداختن",
     "details.owner_options": "مدیر گروه",
@@ -41,6 +41,7 @@
     "details.hidden": "پنهان",
     "details.hidden_help": "اگر فعال باشد، این گروه در فهرست گروه‌ها پیدا نمی‌شود و کاربران باید دستی فراخوانده شوند",
     "details.delete_group": "حذف گروه",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "جزییات گروه با موفقیت به روز شد",
     "event.deleted": "گروه \"%1\" حدف شد",
     "membership.accept-invitation": "دعوت را قبول میکنم",
@@ -48,5 +49,6 @@
     "membership.join-group": "ورود به گروه",
     "membership.leave-group": "خروج از گروه",
     "membership.reject": "رد",
-    "new-group.group_name": "نام گروه"
+    "new-group.group_name": "نام گروه",
+    "upload-group-cover": "آپلود کاور گروه"
 }
\ No newline at end of file
diff --git a/public/language/fa_IR/modules.json b/public/language/fa_IR/modules.json
index c588350c40..cae06f83b0 100644
--- a/public/language/fa_IR/modules.json
+++ b/public/language/fa_IR/modules.json
@@ -6,8 +6,9 @@
     "chat.user_typing": "%1 در حال نوشتن است...",
     "chat.user_has_messaged_you": "%1 به شما پیام داده است.",
     "chat.see_all": "دیدن همه ی چت ها",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "مشخص کنید تاریخچه چتهایتان با چه کاربری را می‌خواهید ببینید",
-    "chat.no-users-in-room": "No users in this room",
+    "chat.no-users-in-room": "هیچ کاربری در این گفتگو نیست",
     "chat.recent-chats": "چتهای اخیر",
     "chat.contacts": "تماس‌ها",
     "chat.message-history": "تاریخچه پیام‌ها",
diff --git a/public/language/fa_IR/notifications.json b/public/language/fa_IR/notifications.json
index e5e04b6461..ee4641d006 100644
--- a/public/language/fa_IR/notifications.json
+++ b/public/language/fa_IR/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> پست شما را به <strong>%2</strong> انتقال داده است",
     "moved_your_topic": "<strong>%2</strong> <strong>%1</strong> را منتقل کرده است",
-    "favourited_your_post_in": "<strong>%1</strong> پست شما را در <strong>%2</strong> محبوب کرده.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> پست شما را در <strong>%2</strong> علامتدار کرده",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -27,9 +27,10 @@
     "user_posted_to_multiple": "<strong>%1</strong> and %2 others have posted replies to: <strong>%3</strong>",
     "user_posted_topic": "<strong>%1</strong> یک موضوع جدید ارسال کرده: <strong>%2</strong>",
     "user_started_following_you": "<strong>%1</strong> شروع به دنبال کردن شما کرده",
-    "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
+    "user_started_following_you_dual": "<strong>%1</strong> و <strong>%2</strong> شروع به دنبال کردن شما کرده.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> یک درخواست ثبت نام ارسال کرده است",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "ایمیل تایید شد",
     "email-confirmed-message": "بابت تایید ایمیلتان سپاس‌گزاریم. حساب کاربری شما اکنون به صورت کامل فعال شده است.",
     "email-confirm-error-message": "خطایی در تایید آدرس ایمیل شما پیش آمده است. ممکن است کد نا‌معتبر و یا منقضی شده باشد.",
diff --git a/public/language/fa_IR/pages.json b/public/language/fa_IR/pages.json
index 050764ba2e..06d6c1ec35 100644
--- a/public/language/fa_IR/pages.json
+++ b/public/language/fa_IR/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "پست‌های %1",
     "account/topics": "موضوع های %1",
     "account/groups": "گروه‌های %1",
-    "account/favourites": "پست‌های محبوب شده %1",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "تنظیمات کاربر",
     "account/watched": "موضوع های دیده شده توسط \"%1\"",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 در حال حاضر تحت تعمیر و نگهدارییست. لطفا زمان دیگری مراجعه کنید.",
     "maintenance.messageIntro": "علاوه بر این، مدیر این پیام را گذاشته است:",
     "throttled.text": "%1 به دلیل بارگذاری بیش از حد ، قابل دسترس نمی باشد. لطفا در زمان دیگری دوباره امتحان کنید"
diff --git a/public/language/fa_IR/topic.json b/public/language/fa_IR/topic.json
index 74dabdce53..f3265b1ca9 100644
--- a/public/language/fa_IR/topic.json
+++ b/public/language/fa_IR/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "دسته‌های از کار افتاده به رنگ خاکستری در می‌آیند",
     "confirm_move": "جابه‌جا کردن",
     "confirm_fork": "شاخه ساختن",
-    "favourite": "محبوب کردن",
-    "favourites": "محبوبها",
-    "favourites.has_no_favourites": "شماهیچ موضوع محبوبی ندارید، چندین موضوع را محبوب کنید تا آن‌ها را در اینجا ببینید.",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "بارگذاری پست‌های بیش‌تر",
     "move_topic": "جابه‌جایی موضوع",
     "move_topics": "انتقال موضوع",
diff --git a/public/language/fa_IR/uploads.json b/public/language/fa_IR/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/fa_IR/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json
index 6905c78acd..57d8e534b7 100644
--- a/public/language/fa_IR/user.json
+++ b/public/language/fa_IR/user.json
@@ -22,7 +22,7 @@
     "profile": "پروفایل",
     "profile_views": "بازدیدهای نمایه",
     "reputation": "اعتبار",
-    "favourites": "محبوبها",
+    "favourites": "Bookmarks",
     "watched": "پیگیری شده",
     "followers": "دنبال‌کننده‌ها",
     "following": "دنبال‌شونده‌ها",
@@ -39,6 +39,7 @@
     "change_username": "تغییر نام کاربری",
     "change_email": "تغییر ایمیل",
     "edit": "ویرایش",
+    "edit-profile": "Edit Profile",
     "default_picture": "آیکون پیش فرض",
     "uploaded_picture": "تصویر بارشده",
     "upload_new_picture": "بارگذاری تصویر تازه",
@@ -55,10 +56,11 @@
     "password": "گذرواژه",
     "username_taken_workaround": "نام کاربری درخواستی شما در حال حاضر گرفته شده است، بنابراین ما آن را کمی تغییر داده‌ایم. شما هم‌اکنون با نام <strong>%1</strong  شناخته می‌شوید.",
     "password_same_as_username": "کلمه ی عبور شما با نام کاربری شما یکسان می باشند ، لطفا کلمه ی عبور دیگری را انتخاب کنید",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "بارگذاری تصویر",
     "upload_a_picture": "یک تصویر بارگذاری کنید",
     "remove_uploaded_picture": "پاک کردن عکس بارگذاری شده",
-    "image_spec": "فرمت عکس های شما باید یکی از فرمتهای PNG , JPG, BMP باشد",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "تنظیمات",
     "show_email": "نمایش ایمیل‌ام",
     "show_fullname": "نام کامل من را نشان بده",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "پیوندهای به بیرون را در برگ جدید باز کن",
     "enable_topic_searching": "فعال کردن جستجوی داخل-موضوع",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "تاپیک هایی که پاسخ داده ای را دنبال کن",
     "follow_topics_you_create": "موضوع هایی که ایجاد کرده ای را دنبال کن",
     "grouptitle": "عنوان گروهی که میخواهید نشان داده شود را انتخاب کنید.",
diff --git a/public/language/fi/category.json b/public/language/fi/category.json
index ca4d44ca95..0083f12e8b 100644
--- a/public/language/fi/category.json
+++ b/public/language/fi/category.json
@@ -1,5 +1,5 @@
 {
-    "category": "Category",
+    "category": "Kategoria",
     "subcategories": "Subcategories",
     "new_topic_button": "Uusi aihe",
     "guest-login-post": "Kirjaudu sisään voidaksesi kirjoittaa viesti",
diff --git a/public/language/fi/email.json b/public/language/fi/email.json
index e53ac44919..92002dae56 100644
--- a/public/language/fi/email.json
+++ b/public/language/fi/email.json
@@ -1,30 +1,30 @@
 {
     "password-reset-requested": "Pyydetty salasanan palautuskoodia - %1!",
     "welcome-to": "%1 - Tervetuloa",
-    "invite": "Invitation from %1",
+    "invite": "Kutsu henkilöltä %1",
     "greeting_no_name": "Hei",
     "greeting_with_name": "Hei %1",
     "welcome.text1": "Kiitos rekisteröitymisestäsi sivustolle %1!",
     "welcome.text2": "Meidän täytyy varmistaa, että omistat sen sähköpostiosoitteen, jolla rekisteröidyit, ennen kuin tilisi voidaan aktivoida kokonaan.",
-    "welcome.text3": "An administrator has accepted your registration application. You can login with your username/password now.",
+    "welcome.text3": "Ylläpitäjä hyväksyi rekisteröintipyyntösi. Voit nyt kirjautua käyttäjänimelläsi ja salasanallasi.",
     "welcome.cta": "Napsauta tästä vahvistaaksesi sähköpostiosoitteesi",
-    "invitation.text1": "%1 has invited you to join %2",
-    "invitation.ctr": "Click here to create your account.",
-    "reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.",
-    "reset.text2": "To continue with the password reset, please click on the following link:",
-    "reset.cta": "Click here to reset your password",
-    "reset.notify.subject": "Password successfully changed",
-    "reset.notify.text1": "We are notifying you that on %1, your password was changed successfully.",
-    "reset.notify.text2": "If you did not authorise this, please notify an administrator immediately.",
-    "digest.notifications": "You have unread notifications from %1:",
-    "digest.latest_topics": "Latest topics from %1",
+    "invitation.text1": "%1 pyysi sinua liittymään %2",
+    "invitation.ctr": "Napsauta tästä luodaksesi käyttäjätilisi.",
+    "reset.text1": "Saimme pyynnön vaihtaa salasanasi, todennäkösesti koska olit unohtanut sen. Jos näin ei ollut käynyt, voit jättää tämän viestin huomiotta.",
+    "reset.text2": "Jatkaaksesi salasanan nollausta, napsauta seuraavaa linkkiä:",
+    "reset.cta": "Napsauta tästä vaihtaaksesi salasanasi",
+    "reset.notify.subject": "Salasana onnistuneesti vaihdettu",
+    "reset.notify.text1": "Ilmoitamme sinua että %1, salasanasi vaihdettiin onnistuneesti.",
+    "reset.notify.text2": "Jos et tunnista tätä toimintoa, ilmoita välittömästi ylläpitäjälle.",
+    "digest.notifications": "Sinulla on lukemattomia ilmoituksia: %1:",
+    "digest.latest_topics": "Viimeisimmät viestiketjut henkilöltä %1",
     "digest.cta": "Click here to visit %1",
     "digest.unsub.info": "This digest was sent to you due to your subscription settings.",
     "digest.no_topics": "There have been no active topics in the past %1",
-    "digest.day": "day",
-    "digest.week": "week",
-    "digest.month": "month",
-    "notif.chat.subject": "New chat message received from %1",
+    "digest.day": "päivä",
+    "digest.week": "viikko",
+    "digest.month": "kuukausi",
+    "notif.chat.subject": "Uusi chatviesti henkilöltä %1",
     "notif.chat.cta": "Click here to continue the conversation",
     "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.",
     "notif.post.cta": "Click here to read the full topic",
diff --git a/public/language/fi/error.json b/public/language/fi/error.json
index 2a07ffd56d..dbb4fd10c2 100644
--- a/public/language/fi/error.json
+++ b/public/language/fi/error.json
@@ -18,7 +18,7 @@
     "username-taken": "Käyttäjänimi varattu",
     "email-taken": "Sähköpostiosoite varattu",
     "email-not-confirmed": "Sähköpostiasi ei ole vielä vahvistettu, ole hyvä ja napsauta tätä vahvistaaksesi sen.",
-    "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.",
+    "email-not-confirmed-chat": "Et voi keskustella ennen kuin sähköpostiosoitteesi on vahvistettu, ole hyvä ja paina tästä vahvistaaksesi sen.",
     "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email",
     "email-confirm-failed": "We could not confirm your email, please try again later.",
     "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.",
@@ -26,7 +26,8 @@
     "username-too-long": "Käyttäjänimi on liian pitkä",
     "password-too-long": "Password too long",
     "user-banned": "Käyttäjä on estetty",
-    "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "user-too-new": "Anteeksi, mutta sinun täytyy odottaa %1 sekunti(a) ennen sinun ensimmäisen viestin lähettämistä",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategoriaa ei ole olemassa",
     "no-topic": "Aihetta ei ole olemassa",
     "no-post": "Viestiä ei ole olemassa",
@@ -37,10 +38,10 @@
     "category-disabled": "Kategoria ei ole käytössä",
     "topic-locked": "Aihe lukittu",
     "post-edit-duration-expired": "You are only allowed to edit posts for %1 second(s) after posting",
-    "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).",
-    "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).",
-    "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).",
-    "title-too-long": "Please enter a shorter title. Titles can't be longer than %1 character(s).",
+    "content-too-short": "Ole hyvä ja syötä pidempi viesti. Sen pitäisi sisältää ainakin %1 merkki(ä).",
+    "content-too-long": "Ole hyvä ja syötä lyhyempi viesti. Sen voi sisältää vain %1 merkki(ä).",
+    "title-too-short": "Ole hyä ja syötä pidempi otsikko. Sen pitäisi sisältää anakin %1 merkki(ä).",
+    "title-too-long": "Ole hyvä ja syötä lyhyempi otsikko. Se voi sisältää vain %1 merkki(ä).",
     "too-many-posts": "You can only post once every %1 second(s) - please wait before posting again",
     "too-many-posts-newbie": "As a new user, you can only post once every %1 second(s) until you have earned %2 reputation - please wait before posting again",
     "tag-too-short": "Please enter a longer tag. Tags should contain at least %1 character(s)",
@@ -50,8 +51,8 @@
     "still-uploading": "Ole hyvä ja odota tiedostojen lähettämisen valmistumista.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Tämä viesti on jo suosikeissasi",
-    "already-unfavourited": "Olet jo poistanut tämän viestin suosikeistasi",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Et voi estää muita ylläpitäjiä!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -77,10 +78,10 @@
     "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).",
     "cant-chat-with-yourself": "Et voi keskustella itsesi kanssa!",
     "chat-restricted": "This user has restricted their chat messages. They must follow you before you can chat with them",
-    "chat-disabled": "Chat system disabled",
+    "chat-disabled": "Keskustelujärjestelmä on pois käytöstä",
     "too-many-messages": "You have sent too many messages, please wait awhile.",
-    "invalid-chat-message": "Invalid chat message",
-    "chat-message-too-long": "Chat message is too long",
+    "invalid-chat-message": "Virheellinen keskusteluviesti",
+    "chat-message-too-long": "Keskusteluviesti on liian pitkä",
     "cant-edit-chat-message": "You are not allowed to edit this message",
     "cant-remove-last-user": "You can't remove the last user",
     "cant-delete-chat-message": "You are not allowed to delete this message",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "Käyttäjä ei ole huoneessa",
+    "no-users-in-room": "Ei käyttäjiä tässä huoneessa",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/fi/global.json b/public/language/fi/global.json
index 882b22c137..94d68b78fb 100644
--- a/public/language/fi/global.json
+++ b/public/language/fi/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Poista kaikki",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/fi/groups.json b/public/language/fi/groups.json
index 9e136f5948..55e7b41f2e 100644
--- a/public/language/fi/groups.json
+++ b/public/language/fi/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json
index 4b514d8678..978d306170 100644
--- a/public/language/fi/modules.json
+++ b/public/language/fi/modules.json
@@ -6,8 +6,9 @@
     "chat.user_typing": "%1 kirjoittaa ...",
     "chat.user_has_messaged_you": "%1 lähetti sinulle viestin.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Valitse vastaanottaja katsellaksesi keskusteluhistoriaa",
-    "chat.no-users-in-room": "No users in this room",
+    "chat.no-users-in-room": "Ei käyttäjiä tässä huoneessa",
     "chat.recent-chats": "Viimeisimmät keskustelut",
     "chat.contacts": "Contacts",
     "chat.message-history": "Viestihistoria",
diff --git a/public/language/fi/notifications.json b/public/language/fi/notifications.json
index efa0351f45..427e04311e 100644
--- a/public/language/fi/notifications.json
+++ b/public/language/fi/notifications.json
@@ -1,11 +1,11 @@
 {
     "title": "Ilmoitukset",
     "no_notifs": "Sinulla ei ole uusia ilmoituksia",
-    "see_all": "See all notifications",
-    "mark_all_read": "Mark all notifications read",
-    "back_to_home": "Back to %1",
+    "see_all": "Katso kaikki ilmoitukset",
+    "mark_all_read": "Merkkaa kaikki ilmoitukset luetuiksi",
+    "back_to_home": "Palaa takaisin %1",
     "outgoing_link": "Ulkopuolinen linkki",
-    "outgoing_link_message": "You are now leaving %1",
+    "outgoing_link_message": "Olet nyt poistumassa %1",
     "continue_to": "Continue to %1",
     "return_to": "Return to %1",
     "new_notification": "Uusi ilmoitus",
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Sähköpostiosoite vahvistettu",
     "email-confirmed-message": "Kiitos sähköpostiosoitteesi vahvistamisesta. Käyttäjätilisi on nyt täysin aktivoitu.",
     "email-confirm-error-message": "Ongelma sähköpostiosoitteen vahvistamisessa. Ehkäpä koodi oli virheellinen tai vanhentunut.",
diff --git a/public/language/fi/pages.json b/public/language/fi/pages.json
index e25e4f740e..d2eba81a6c 100644
--- a/public/language/fi/pages.json
+++ b/public/language/fi/pages.json
@@ -1,44 +1,45 @@
 {
     "home": "Etusivu",
     "unread": "Lukemattomat aiheet",
-    "popular-day": "Popular topics today",
-    "popular-week": "Popular topics this week",
-    "popular-month": "Popular topics this month",
-    "popular-alltime": "All time popular topics",
+    "popular-day": "Suositut aiheet tänään",
+    "popular-week": "Suositut aiheet tällä viikolla",
+    "popular-month": "Suositut aiheet tässä kuussa",
+    "popular-alltime": "Suositut aiheet koko ajalta",
     "recent": "Viimeisimmät aiheet",
-    "flagged-posts": "Flagged Posts",
-    "users/online": "Online Users",
-    "users/latest": "Latest Users",
+    "flagged-posts": "Liputetut viestit",
+    "users/online": "Paikalla olevat käyttäjät",
+    "users/latest": "Viimeisimmat käyttäjät",
     "users/sort-posts": "Users with the most posts",
     "users/sort-reputation": "Users with the most reputation",
     "users/banned": "Banned Users",
     "users/search": "User Search",
     "notifications": "Ilmoitukset",
-    "tags": "Tags",
+    "tags": "Tunnisteet",
     "tag": "Topics tagged under \"%1\"",
-    "register": "Register an account",
-    "login": "Login to your account",
+    "register": "Luo käyttäjät",
+    "login": "Kirjaudu käyttäjällesi",
     "reset": "Reset your account password",
     "categories": "Categories",
     "groups": "Groups",
     "group": "%1 group",
-    "chats": "Chats",
+    "chats": "Keskustelut",
     "chat": "Chatting with %1",
     "account/edit": "Editing \"%1\"",
     "account/edit/password": "Editing password of \"%1\"",
     "account/edit/username": "Editing username of \"%1\"",
     "account/edit/email": "Editing email of \"%1\"",
-    "account/following": "People %1 follows",
-    "account/followers": "People who follow %1",
+    "account/following": "Ihmiset, jota %1 seuraa",
+    "account/followers": "Ihmiset, jotka seuraavat %1",
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json
index 066d0256a5..37b9b98e80 100644
--- a/public/language/fi/topic.json
+++ b/public/language/fi/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Käytöstä poistetut aihealueet ovat harmaina",
     "confirm_move": "Siirrä",
     "confirm_fork": "Haaroita",
-    "favourite": "Lisää suosikiksi",
-    "favourites": "Suosikit",
-    "favourites.has_no_favourites": "Sinulla ei ole yhtään suosikkiviestiä. Lisää joitakin viestejä suosikeiksi nähdäksesi ne täällä!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Ladataan lisää viestejä",
     "move_topic": "Siirrä aihe",
     "move_topics": "Move Topics",
diff --git a/public/language/fi/uploads.json b/public/language/fi/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/fi/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/fi/user.json b/public/language/fi/user.json
index c7e1232c35..f7218528a3 100644
--- a/public/language/fi/user.json
+++ b/public/language/fi/user.json
@@ -22,7 +22,7 @@
     "profile": "Profiili",
     "profile_views": "Profiilia katsottu",
     "reputation": "Maine",
-    "favourites": "Suosikit",
+    "favourites": "Bookmarks",
     "watched": "Seurattu",
     "followers": "Seuraajat",
     "following": "Seuratut",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Muokkaa",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Ladattu kuva",
     "upload_new_picture": "Lataa uusi kuva",
@@ -55,10 +56,11 @@
     "password": "Salasana",
     "username_taken_workaround": "Pyytämäsi käyttäjänimi oli jo varattu, joten muutimme sitä hieman. Käyttäjänimesi on siis nyt <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Lataa kuva",
     "upload_a_picture": "Lataa kuva",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Asetukset",
     "show_email": "Näytä sähköpostiosoitteeni",
     "show_fullname": "Näytä koko nimeni",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Salli aiheen sisäiset haut",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/fi/users.json b/public/language/fi/users.json
index 1885be650e..9ebaf393e1 100644
--- a/public/language/fi/users.json
+++ b/public/language/fi/users.json
@@ -15,6 +15,6 @@
     "popular_topics": "Popular Topics",
     "unread_topics": "Unread Topics",
     "categories": "Categories",
-    "tags": "Tags",
+    "tags": "Tunnisteet",
     "no-users-found": "No users found!"
 }
\ No newline at end of file
diff --git a/public/language/fr/error.json b/public/language/fr/error.json
index b372224e92..d80c43277e 100644
--- a/public/language/fr/error.json
+++ b/public/language/fr/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Mot de passe invalide",
     "invalid-username-or-password": "Veuillez entrer un nom d'utilisateur et un mot de passe",
     "invalid-search-term": "Données de recherche invalides",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Valeur de pagination invalide. Celle-ci doit être comprise entre %1 et %2.",
     "username-taken": "Nom d’utilisateur déjà utilisé",
     "email-taken": "Email déjà utilisé",
     "email-not-confirmed": "Votre adresse email n'est pas confirmée, cliquez ici pour la valider.",
@@ -27,6 +27,7 @@
     "password-too-long": "Mot de passe trop long",
     "user-banned": "Utilisateur banni",
     "user-too-new": "Désolé, vous devez attendre encore %1 seconde(s) avant d'envoyer votre premier message",
+    "blacklisted-ip": "Désolé, votre adresse IP a été bannie de cette communauté. Si vous pensez que c'est une erreur, veuillez contacter un administrateur.",
     "no-category": "Cette catégorie n'existe pas",
     "no-topic": "Ce sujet n'existe pas",
     "no-post": "Ce message n'existe pas",
@@ -50,8 +51,8 @@
     "still-uploading": "Veuillez patienter pendant le téléchargement.",
     "file-too-big": "La taille maximale autorisée pour un fichier est de %1 kb. Veuillez envoyer un fichier plus petit.",
     "guest-upload-disabled": "Le téléversement est désactivé pour les Invités.",
-    "already-favourited": "Vous avez déjà mis ce message en favoris",
-    "already-unfavourited": "Vous avez déjà retiré ce message des favoris",
+    "already-favourited": "Vous avez déjà mis un marque-page",
+    "already-unfavourited": "Vous avez déjà retiré un marque-page",
     "cant-ban-other-admins": "Vous ne pouvez pas bannir les autres administrateurs !",
     "cant-remove-last-admin": "Vous seul êtes administrateur. Ajouter un autre utilisateur en tant qu'administrateur avant de vous en retirer.",
     "invalid-image-type": "Type d'image invalide. Les types autorisés sont: %1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Le message de Chat est trop long",
     "cant-edit-chat-message": "Vous n'avez pas l'autorisation de modifier ce message",
     "cant-remove-last-user": "Vous ne pouvez pas supprimer le dernier utilisateur",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Vous n'avez pas l'autorisation de supprimer ce message",
     "reputation-system-disabled": "Le système de réputation est désactivé",
     "downvoting-disabled": "Les votes négatifs ne sont pas autorisés",
     "not-enough-reputation-to-downvote": "Vous n'avez pas une réputation assez élevée pour noter négativement ce message",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Veuillez utiliser votre identifiant pour vous connecter",
     "invite-maximum-met": "Vous avez invité la quantité maximale de personnes (%1 de %2).",
     "no-session-found": "Pas de session de connexion trouvé!",
-    "not-in-room": "User not in room"
+    "not-in-room": "L'utilisateur n'est pas dans cette salle",
+    "no-users-in-room": "Aucun utilisateur dans cette salle",
+    "cant-kick-self": "Vous ne pouvez pas vous exclure vous-même du groupe"
 }
\ No newline at end of file
diff --git a/public/language/fr/global.json b/public/language/fr/global.json
index aaabd06939..7096b63f7c 100644
--- a/public/language/fr/global.json
+++ b/public/language/fr/global.json
@@ -29,13 +29,13 @@
     "header.popular": "Populaire",
     "header.users": "Utilisateurs",
     "header.groups": "Groupes",
-    "header.chats": "Chats",
+    "header.chats": "Discussions",
     "header.notifications": "Notifications",
     "header.search": "Recherche",
     "header.profile": "Profil",
     "header.navigation": "Navigation",
     "notifications.loading": "Chargement des notifications",
-    "chats.loading": "Chargement des chats",
+    "chats.loading": "Chargement des discussions",
     "motd.welcome": "Bienvenue sur NodeBB, la plate-forme de discussion du futur.",
     "previouspage": "Page précédente",
     "nextpage": "Page suivante",
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "posté dans %1 %2 par %3",
     "user_posted_ago": "%1 a posté %2",
     "guest_posted_ago": "Un invité a posté %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "dernière édition par %1",
     "norecentposts": "Aucun message récent",
     "norecenttopics": "Aucun sujet récent",
     "recentposts": "Messages récents",
@@ -86,5 +86,9 @@
     "delete_all": "Tout supprimer",
     "map": "Carte",
     "sessions": "Sessions de connexion",
-    "ip_address": "Adresse IP"
+    "ip_address": "Adresse IP",
+    "enter_page_number": "Entrer un numéro de page",
+    "upload_file": "Envoyer un fichier",
+    "upload": "Envoyer",
+    "allowed-file-types": "Les types de fichiers autorisés sont : %1"
 }
\ No newline at end of file
diff --git a/public/language/fr/groups.json b/public/language/fr/groups.json
index 6ffb22ecfa..65a5274979 100644
--- a/public/language/fr/groups.json
+++ b/public/language/fr/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Masqué",
     "details.hidden_help": "Si cette case est cochée, ce groupe n'est pas affiché dans la liste des groupes, et les utilisateurs devront être invités manuellement.",
     "details.delete_group": "Supprimer le groupe",
+    "details.private_system_help": "Les groupes privés sont désactivés au niveau du système, cette option ne déclenche rien",
     "event.updated": "Les détails du groupe ont été mis à jour",
     "event.deleted": "Le groupe \"%1\" a été supprimé",
     "membership.accept-invitation": "Accepter l'invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Rejoindre le groupe",
     "membership.leave-group": "Quitter le groupe",
     "membership.reject": "Refuser",
-    "new-group.group_name": "Nom du groupe :"
+    "new-group.group_name": "Nom du groupe :",
+    "upload-group-cover": "Envoyer une image de groupe"
 }
\ No newline at end of file
diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json
index 248c716be4..33fd5fe494 100644
--- a/public/language/fr/modules.json
+++ b/public/language/fr/modules.json
@@ -1,13 +1,14 @@
 {
     "chat.chatting_with": "Discuter avec <span id=\"chat-with-name\"></span>",
-    "chat.placeholder": "Tapez votre message ici, appuyez sur Entrer pour envoyer",
+    "chat.placeholder": "Tapez votre message ici, appuyez sur Entrée pour envoyer",
     "chat.send": "Envoyer",
     "chat.no_active": "Vous n'avez aucune discussion en cours.",
     "chat.user_typing": "%1 est en train d'écrire ...",
     "chat.user_has_messaged_you": "%1 vous a envoyé un message.",
     "chat.see_all": "Voir toutes les discussions",
+    "chat.mark_all_read": "Marquer toutes les discussions comme lues",
     "chat.no-messages": "Veuillez sélectionner un destinataire pour voir l'historique des discussions",
-    "chat.no-users-in-room": "Aucun utilisateur dans cette salle",
+    "chat.no-users-in-room": "Aucun participant à cette discussion",
     "chat.recent-chats": "Discussions récentes",
     "chat.contacts": "Contacts",
     "chat.message-history": "Historique des messages",
@@ -18,7 +19,7 @@
     "chat.three_months": "3 Mois",
     "chat.delete_message_confirm": "Êtes-vous sûr de vouloir supprimer ce message ?",
     "chat.roomname": "Salle de discussion %1",
-    "chat.add-users-to-room": "Ajouter des utilisateurs à la salle",
+    "chat.add-users-to-room": "Ajouter des participants",
     "composer.compose": "Écrire",
     "composer.show_preview": "Afficher l'aperçu",
     "composer.hide_preview": "Masquer l'aperçu",
diff --git a/public/language/fr/notifications.json b/public/language/fr/notifications.json
index 02dbd67398..fadee3aa62 100644
--- a/public/language/fr/notifications.json
+++ b/public/language/fr/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> et %2 autres on voté pour votre message dans <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> a déplacé votre message vers <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> a déplacé <strong>%2</strong>.",
-    "favourited_your_post_in": "<strong>%1</strong> a mis votre message en favoris dans <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> et <strong>%2</strong> ont mis votre message dans <strong>%3</strong> en favori.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> et %2 autres on mis votre message en favori <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> a enregistré votre message dans <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> et <strong>%2</strong> autres ont enregistré votre message dans <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> et %2 autres ont enregistré votre message dans <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> a signalé un message dans <strong>%2</strong>.",
     "user_flagged_post_in_dual": "<strong>%1</strong> et <strong>%2</strong> ont signalé un message dans <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> et %2 autres on signalé un message dans <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> et <strong>%2</strong> vous suivent.",
     "user_started_following_you_multiple": "<strong>%1</strong> et %2 autres vous suivent.",
     "new_register": "<strong>%1</strong> a envoyé une demande d'incription.",
+    "new_register_multiple": "<strong>%1</strong> inscription(s) est en attente de validation.",
     "email-confirmed": "Email vérifié",
     "email-confirmed-message": "Merci pour la validation de votre adresse email. Votre compte est désormais activé.",
     "email-confirm-error-message": "Il y a un un problème dans la vérification de votre adresse email. Le code est peut être invalide ou a expiré.",
diff --git a/public/language/fr/pages.json b/public/language/fr/pages.json
index 5726e2d1cb..3c0cb29b90 100644
--- a/public/language/fr/pages.json
+++ b/public/language/fr/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Sujets populaires ce mois-ci",
     "popular-alltime": "Sujets populaires depuis toujours",
     "recent": "Sujets récents",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Messages signalés",
     "users/online": "Utilisateurs en ligne",
     "users/latest": "Derniers inscrits",
     "users/sort-posts": "Utilisateurs avec le plus de messages",
     "users/sort-reputation": "Utilisateurs avec la plus grande réputation",
-    "users/banned": "Banned Users",
+    "users/banned": "Utilisateurs bannis",
     "users/search": "Rechercher des utilisateurs",
     "notifications": "Notifications",
     "tags": "Mots-clés",
@@ -33,12 +33,13 @@
     "account/posts": "Messages postés par %1",
     "account/topics": "Sujets créés par %1",
     "account/groups": "Groupes auxquels appartient %1",
-    "account/favourites": "Messages favoris de %1",
+    "account/favourites": "Marques-pages de %1",
     "account/settings": "Paramètres d'utilisateur",
     "account/watched": "Sujets surveillés par %1",
     "account/upvoted": "Messages pour lesquels %1 a voté",
     "account/downvoted": "Messages contre lesquels %1 a voté",
     "account/best": "Meilleurs messages postés par %1",
+    "confirm": "Email vérifié",
     "maintenance.text": "%1 est en maintenance. Veuillez revenir un peu plus tard.",
     "maintenance.messageIntro": "De plus, l'administrateur a laissé ce message:",
     "throttled.text": "%1 est actuellement indisponible en raison d'une charge excessive. Merci de réessayer plus tard."
diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json
index 318acf7f65..8a71ff511a 100644
--- a/public/language/fr/topic.json
+++ b/public/language/fr/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Veuillez vous enregistrer ou vous connecter afin de vous abonner à ce sujet.",
     "markAsUnreadForAll.success": "Sujet marqué comme non lu pour tout le monde.",
     "mark_unread": "Marquer comme non-lu",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Sujet marqué comme non lu.",
     "watch": "Surveiller",
     "unwatch": "Ne plus surveiller",
     "watch.title": "Être notifié des nouvelles réponses dans ce sujet",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Les catégories désactivées sont grisées",
     "confirm_move": "Déplacer",
     "confirm_fork": "Scinder",
-    "favourite": "Favori",
-    "favourites": "Favoris",
-    "favourites.has_no_favourites": "Vous n'avez aucun favori, mettez des messages en favoris pour les voir ici !",
+    "favourite": "Marque-page",
+    "favourites": "Marque-page",
+    "favourites.has_no_favourites": "Vous n'avez aucuns marque-page.",
     "loading_more_posts": "Charger plus de messages",
     "move_topic": "Déplacer le sujet",
     "move_topics": "Déplacer des sujets",
@@ -105,7 +105,7 @@
     "stale.warning": "Le sujet auquel vous répondez est assez ancien. Ne voudriez-vous pas créer un nouveau sujet à la place et placer une référence vers celui-ci dans votre réponse ?",
     "stale.create": "Créer un nouveau sujet",
     "stale.reply_anyway": "Répondre à ce sujet quand même",
-    "link_back": "Re: [%1](%2)",
+    "link_back": "Re : [%1](%2)",
     "spam": "Spam",
     "offensive": "Offensif",
     "custom-flag-reason": "Entrez une raison pour laquelle vous signalez ce message"
diff --git a/public/language/fr/uploads.json b/public/language/fr/uploads.json
new file mode 100644
index 0000000000..7cc22992da
--- /dev/null
+++ b/public/language/fr/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Envoi du fichier…",
+    "select-file-to-upload": "Sélectionnez un ficher à envoyer",
+    "upload-success": "Fichier envoyé",
+    "maximum-file-size": "%1 Ko maximum"
+}
\ No newline at end of file
diff --git a/public/language/fr/user.json b/public/language/fr/user.json
index f11322f2c9..0a6450fdd3 100644
--- a/public/language/fr/user.json
+++ b/public/language/fr/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Vues",
     "reputation": "Réputation",
-    "favourites": "Favoris",
+    "favourites": "Marque-pages",
     "watched": "Suivis",
     "followers": "Abonnés",
     "following": "Abonnements",
@@ -39,6 +39,7 @@
     "change_username": "Changer le nom d'utilisateur",
     "change_email": "Changer l'e-mail",
     "edit": "Éditer",
+    "edit-profile": "Éditer le profil",
     "default_picture": "Icône par défaut",
     "uploaded_picture": "Image envoyée",
     "upload_new_picture": "Envoyer une nouvelle image",
@@ -55,10 +56,11 @@
     "password": "Mot de passe",
     "username_taken_workaround": "Le nom d'utilisateur désiré est déjà utilisé, nous l'avons donc légèrement modifié. Vous êtes maintenant connu comme <strong>%1</strong>",
     "password_same_as_username": "Votre mot de passe est identique à votre nom d'utilisateur. Veuillez en choisir un autre.",
+    "password_same_as_email": "Votre mot de passe est identique à votre adresse email. Veuillez en choisir un autre.",
     "upload_picture": "Envoyer l'image",
     "upload_a_picture": "Envoyer une image",
     "remove_uploaded_picture": "Supprimer l'image envoyée",
-    "image_spec": "Vous ne pouvez envoyer que des fichiers PNG, JPG ou BMP",
+    "upload_cover_picture": "Envoyer une image de couverture",
     "settings": "Paramètres",
     "show_email": "Afficher mon email",
     "show_fullname": "Afficher mon nom complet",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Ouvrir les liens externes dans un nouvel onglet",
     "enable_topic_searching": "Activer la recherche dans les sujets",
     "topic_search_help": "Une fois activé, la recherche dans les sujets va remplacer la recherche de page du navigateur et vous permettra de rechercher dans l'intégralité d'un sujet au lieu des seuls posts affichés à l'écran.",
+    "scroll_to_my_post": "Après avoir répondu, montrer le nouveau message",
     "follow_topics_you_reply_to": "Suivre les sujets auxquels vous répondez",
     "follow_topics_you_create": "Suivre les sujets que vous créez",
     "grouptitle": "Sélectionnez le titre de groupe que vous souhaitez afficher",
diff --git a/public/language/fr/users.json b/public/language/fr/users.json
index 9a19daf831..fad9536811 100644
--- a/public/language/fr/users.json
+++ b/public/language/fr/users.json
@@ -3,7 +3,7 @@
     "top_posters": "Actifs",
     "most_reputation": "Réputés",
     "search": "Rechercher",
-    "enter_username": "Entrer un nom d'utilisateur pour rechercher",
+    "enter_username": "Entrez le nom d'un utilisateur",
     "load_more": "Charger la suite",
     "users-found-search-took": "%1 utilisateur(s) trouvé(s)! La recherche a pris %2 secondes.",
     "filter-by": "Filtrer par",
@@ -16,5 +16,5 @@
     "unread_topics": "Sujets Non-Lus",
     "categories": "Catégories",
     "tags": "Mots-clés",
-    "no-users-found": "No users found!"
+    "no-users-found": "Aucun utilisateur trouvé !"
 }
\ No newline at end of file
diff --git a/public/language/gl/error.json b/public/language/gl/error.json
index f17f526582..5a5619eaf0 100644
--- a/public/language/gl/error.json
+++ b/public/language/gl/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Contrasinal Inválido",
     "invalid-username-or-password": "Especifica ámbolos dous por favor, nome de usuario e contrasinal",
     "invalid-search-term": "Termo de búsqueda inválido",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Valor de paxinación incorreto, ten que estar entre %1 e %2",
     "username-taken": "Nome de usuario en uso",
     "email-taken": "Correo en uso",
     "email-not-confirmed": "O teu correo aínda non está confirmado, por favor pica aquí para confirmalo.",
@@ -27,6 +27,7 @@
     "password-too-long": "Contrasinal moi longa",
     "user-banned": "Usuario expulsado",
     "user-too-new": "Desculpa, agarda %1 second(s) antes de facer a túa primeira publicación.",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "A categoría non existe",
     "no-topic": "O tema non existe",
     "no-post": "A publicación non existe",
@@ -50,8 +51,8 @@
     "still-uploading": "Por favor, agarda a que remate a subida.",
     "file-too-big": "O tamaño máximo permitido é %1 kB - por favor, sube un arquivo máis pequeno",
     "guest-upload-disabled": "As subidas están deshabilitadas para os convidados",
-    "already-favourited": "Marcache como favorita esta publicación",
-    "already-unfavourited": "Desmarcache como favorita esta publicación",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Non podes botar outros administradores!",
     "cant-remove-last-admin": "Eres o único administrador. Engade outros administradores antes de quitarte a ti mesmo como administrador.",
     "invalid-image-type": "Tipo de imaxe inválida. Tipos admitidos: %1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Mensaxe moi longa",
     "cant-edit-chat-message": "Non tes permitido editar esta mensaxe.",
     "cant-remove-last-user": "Non podes quitar o último usuario",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Non tes permitido borrar esta mensaxe.",
     "reputation-system-disabled": "O sistema de reputación está deshabilitado",
     "downvoting-disabled": "Os votos negativos están deshabilitados",
     "not-enough-reputation-to-downvote": "Non tes reputación dabonda para esta acción",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Por favor, usa o teu nome de usuario para conectarte",
     "invite-maximum-met": "Convidaches á cantidade máxima de persoas  (%1 de %2).",
     "no-session-found": "Non se atopa ningún inicio de sesión!",
-    "not-in-room": "User not in room"
+    "not-in-room": "O usuario non se encontra nesta sala",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/gl/global.json b/public/language/gl/global.json
index ccb30d5d1f..6ff44536a7 100644
--- a/public/language/gl/global.json
+++ b/public/language/gl/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "Publicado en %1 %2 por %3",
     "user_posted_ago": "%1 publicado %2",
     "guest_posted_ago": "Invitado publicou %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "última edición por %1",
     "norecentposts": "Non hai mensaxes recentes",
     "norecenttopics": "Non hai temas recentes",
     "recentposts": "Mensaxes recentes",
@@ -86,5 +86,9 @@
     "delete_all": "Borrar todo",
     "map": "Mapa",
     "sessions": "Inicios de sesión",
-    "ip_address": "Enderezo IP"
+    "ip_address": "Enderezo IP",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/gl/groups.json b/public/language/gl/groups.json
index cf974c1089..d9b33b659d 100644
--- a/public/language/gl/groups.json
+++ b/public/language/gl/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Oculto",
     "details.hidden_help": "Se está habilitado, este grupo non se poderá atopar na listaxe de grupos e os usuarios deberán ser convidados manualmente.",
     "details.delete_group": "Eliminar Grupo",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Os detalles do grupo foron actualizados",
     "event.deleted": "O grupo \"%1\" foi borrado.",
     "membership.accept-invitation": "Aceptar ",
@@ -48,5 +49,6 @@
     "membership.join-group": "Unirse ao grupo",
     "membership.leave-group": "Marchar do grupo",
     "membership.reject": "Rexeitar",
-    "new-group.group_name": "Nome do grupo:"
+    "new-group.group_name": "Nome do grupo:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json
index 73ca840bdb..a50f0a5a03 100644
--- a/public/language/gl/modules.json
+++ b/public/language/gl/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 está a escribir...",
     "chat.user_has_messaged_you": "%1 enviouche unha mensaxe.",
     "chat.see_all": "Ver tódalas chamadas",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Por favor, seleccione un destinatario para ver o historial das mensaxes ",
     "chat.no-users-in-room": "Non hai usuarios nesta sala",
     "chat.recent-chats": "Charlas Recentes",
diff --git a/public/language/gl/notifications.json b/public/language/gl/notifications.json
index 4bc6bcc706..2796d782a0 100644
--- a/public/language/gl/notifications.json
+++ b/public/language/gl/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> e %2 máis votaron positivamente a túa mensaxe en <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> moveu a túa publicación a<strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> foi movido <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> marcou favorita a túa publicación en <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> y <strong>%2</strong> marcaron como favorita a túa mensaxe en <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> e outras %2 persoas marcaron como favorita a túa mensaxe en <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> reportou unha mensaxe en <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> e <strong>%2</strong> reportaron a túa mensaxe en <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> e outras %2 persoas reportaron unha mensaxe en <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> e <strong>%2</strong> comezaron a seguirte.",
     "user_started_following_you_multiple": "<strong>%1</strong> e %2 máis comezaron a seguirte.",
     "new_register": "<strong>%1</strong> enviou unha petición de rexistro.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Correo confirmado",
     "email-confirmed-message": "Grazas por validar o teu correo. A túa conta agora está activada.",
     "email-confirm-error-message": "Houbo un problema validando o teu correo. Poida que o código fose inválido ou expirase. ",
diff --git a/public/language/gl/pages.json b/public/language/gl/pages.json
index d9ac21ded1..b0a97d55cd 100644
--- a/public/language/gl/pages.json
+++ b/public/language/gl/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Temas populares do mes",
     "popular-alltime": "Temas populares de tódolos tempos",
     "recent": "Temas recentes",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Publicacions Marcadas.",
     "users/online": "Usuarios conectados",
     "users/latest": "Últimos usuarios",
     "users/sort-posts": "Usuarios con máis temas",
     "users/sort-reputation": "Usuarios máis reputados",
-    "users/banned": "Banned Users",
+    "users/banned": "Usuarios Expulsados",
     "users/search": "Búsqueda de usuarios",
     "notifications": "Notificacións",
     "tags": "Etiquetas",
@@ -33,12 +33,13 @@
     "account/posts": "Publicación de %1",
     "account/topics": "Temas de %1",
     "account/groups": "%1's Grupos",
-    "account/favourites": "%1's Publicacións Favoritas",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Opcións de Usuario",
     "account/watched": "Temas vistos por %1",
     "account/upvoted": "Mensaxes votadas positivamente por %1",
     "account/downvoted": "Mensaxes votadas negativamente por %1",
     "account/best": "Mellores mensaxes escritas por %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 está baixo mantemento. Por favor, volve máis tarde.",
     "maintenance.messageIntro": "A máis, o administrador deixou esta mensaxe: ",
     "throttled.text": "&1 non está dispoñible debido a unha carga excesiva. Por favor, volva noutro momento"
diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json
index 2994d605d8..e892d34146 100644
--- a/public/language/gl/topic.json
+++ b/public/language/gl/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Por favor, identifícate para subscribirte a este tema.",
     "markAsUnreadForAll.success": "Publicación marcada como non lida para todos.",
     "mark_unread": "Marcar coma non lido",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Tema marcado como non lido",
     "watch": "Vixiar",
     "unwatch": "Deixar de vixiar",
     "watch.title": "Serás notificado canto haxa novas respostas neste tema",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "As categorías deshabilitadas están en gris",
     "confirm_move": "Mover",
     "confirm_fork": "Dividir",
-    "favourite": "Favorito",
-    "favourites": "Favoritos",
-    "favourites.has_no_favourites": "Non tes favoritos. Podes engadir algún e velos aquí despois!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Cargando máis publicacións",
     "move_topic": "Mover Tema",
     "move_topics": "Mover Temas",
diff --git a/public/language/gl/uploads.json b/public/language/gl/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/gl/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/gl/user.json b/public/language/gl/user.json
index c5ffff6012..014b7aead3 100644
--- a/public/language/gl/user.json
+++ b/public/language/gl/user.json
@@ -22,7 +22,7 @@
     "profile": "Perfil",
     "profile_views": "Visitas ao perfil:",
     "reputation": "Reputación",
-    "favourites": "Favoritos",
+    "favourites": "Bookmarks",
     "watched": "Visto",
     "followers": "Seguidores",
     "following": "Seguindo",
@@ -39,6 +39,7 @@
     "change_username": "Cambia-lo nome de usuario",
     "change_email": "Cambia-lo correo",
     "edit": "Editar",
+    "edit-profile": "Edit Profile",
     "default_picture": "Icona por defecto.",
     "uploaded_picture": "Foto subida",
     "upload_new_picture": "Subir unha nova foto",
@@ -55,10 +56,11 @@
     "password": "Contrasinal",
     "username_taken_workaround": "Ese nome de usuario xa estaba collido, así que o modificamos lixeiramente. Agora o teu nome é <strong>%1</strong> ",
     "password_same_as_username": "O teu contrasinal e o teu nome de usuario son os mesmos, por favor, escolle outro contrasinal.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Subir foto",
     "upload_a_picture": "Subir unha foto",
     "remove_uploaded_picture": "Borrar unha foto subida",
-    "image_spec": "Só podes subir imaxes en formato PNG, JPG ou BMP",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Opcións",
     "show_email": "Amosa-lo meu Email",
     "show_fullname": "Amosa-lo meu Nome Completo",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Abrir ligazóns externos nunca nova pestaña",
     "enable_topic_searching": "Permitir buscar dentro dun tema",
     "topic_search_help": "Se se activa, o buscador do NodeBB superporase ao propio do navegador dentro de cada tema, permitindo buscar en todo o tema e non só naquilo que se presenta na pantalla.",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Segui-los temas nos que respostas",
     "follow_topics_you_create": "Segui-los temas que ti fas",
     "grouptitle": "Escolle o título de grupo que che gustaría amosar",
diff --git a/public/language/gl/users.json b/public/language/gl/users.json
index 0162c1c350..625c85f741 100644
--- a/public/language/gl/users.json
+++ b/public/language/gl/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Temas non lidos",
     "categories": "Categorías",
     "tags": "Etiquetas",
-    "no-users-found": "No users found!"
+    "no-users-found": "Non se atoparon usuarios!"
 }
\ No newline at end of file
diff --git a/public/language/he/error.json b/public/language/he/error.json
index ff63886ec9..33972d8246 100644
--- a/public/language/he/error.json
+++ b/public/language/he/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "סיסמא שגויה",
     "invalid-username-or-password": "אנא הגדר שם משתמש וסיסמה",
     "invalid-search-term": "מילת חיפוש לא תקינה",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "ערך דף לא חוקי, חייב להיות לפחות %1 ולא מעל %2",
     "username-taken": "שם משתמש תפוס",
     "email-taken": "כתובת אימייל תפוסה",
     "email-not-confirmed": "כתובת המייל שלך עוד לא אושרה, לחץ כאן על-מנת לאשר את המייל שלך.",
@@ -27,6 +27,7 @@
     "password-too-long": "הסיסמה ארוכה מדי",
     "user-banned": "המשתמש מושעה",
     "user-too-new": "אנא המתן  %1 שניות לפני פרסום ההודעה",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "קטגוריה אינה קיימת",
     "no-topic": "נושא אינו קיים",
     "no-post": "פוסט אינו קיים",
@@ -40,18 +41,18 @@
     "content-too-short": "אנא הכנס פוסט ארוך יותר. פוסטים חייבים להכיל לפחות %1 תווים.",
     "content-too-long": "אנא הכנס פוסט קצר יותר. פוסטים חייבים להיות קצרים יותר מ-%1 תווים.",
     "title-too-short": "אנא הכנס כותרת ארוכה יותר. כותרות חייבות להכיל לפחות %1 תווים.",
-    "title-too-long": "Please enter a shorter title. Titles can't be longer than %1 character(s).",
-    "too-many-posts": "You can only post once every %1 second(s) - please wait before posting again",
-    "too-many-posts-newbie": "As a new user, you can only post once every %1 second(s) until you have earned %2 reputation - please wait before posting again",
-    "tag-too-short": "Please enter a longer tag. Tags should contain at least %1 character(s)",
-    "tag-too-long": "Please enter a shorter tag. Tags can't be longer than %1 character(s)",
-    "not-enough-tags": "Not enough tags. Topics must have at least %1 tag(s)",
-    "too-many-tags": "Too many tags. Topics can't have more than %1 tag(s)",
+    "title-too-long": "אנא הכנס כותרת קצרה יותר. כותרות אינן יכולות להיות ארוכות מ-%1 תווים.",
+    "too-many-posts": "אתה יכול לפרסם פוסט רק פעם ב-%1 שניות - אנא המתן לפני פרסום שוב",
+    "too-many-posts-newbie": "כמשתמש חדש, אתה יכול לפרסם פוסט רק פעם ב-%1 שניות עד שיהיו לך %2 נקודות מוניטין - אנא המתן לפני פרסום שוב",
+    "tag-too-short": "אנא הכנס תגית ארוכה יותר. תגיות חייבות להכיל לפחות %1 תווים",
+    "tag-too-long": "אנא הכנס תגית קצרה יותר. תגיות אינן יכולות להיות ארוכות יותר מ-%1 תווים",
+    "not-enough-tags": "אין מספיק תגיות. לנושא חייב להיות לפחות %1 תגיות",
+    "too-many-tags": "יותר מדי תגיות. לנושאים לא יכולים להיות יותר מ-%1 תגיות",
     "still-uploading": "אנא המתן לסיום ההעלאות",
-    "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
+    "file-too-big": "הגודל המקסימלי של הקובץ הוא %1 קילובייט - אנא העלה קובץ קטן יותר",
     "guest-upload-disabled": "העלאת אורחים אינה מאופשרת",
-    "already-favourited": "כבר הוספת פוסט זה למועדפים",
-    "already-unfavourited": "כבר הסרת פוסט זה מהמועדפים",
+    "already-favourited": "כבר סימנת את הפוסט הזה",
+    "already-unfavourited": "כבר הסרת את הסימון מפוסט זה",
     "cant-ban-other-admins": "אינך יכול לחסום מנהלים אחרים!",
     "cant-remove-last-admin": "אתה המנהל היחיד. הוסף משתמש אחר לניהול לפני שאתה מוריד את עצמך מניהול",
     "invalid-image-type": "פורמט תמונה לא תקין. הפורמטים המורשים הם: %1",
@@ -64,16 +65,16 @@
     "group-not-member": "לא חבר בקבוצה זו",
     "group-needs-owner": "קבוצה זו חייבת לפחות מנהל אחד",
     "group-already-invited": "משתמש זה כבר הוזמן",
-    "group-already-requested": "Your membership request has already been submitted",
+    "group-already-requested": "בקשת החברות שלך כבר נשלחה",
     "post-already-deleted": "פוסט זה כבר נמחק",
     "post-already-restored": "פוסט זה כבר שוחזר",
     "topic-already-deleted": "נושא זה כבר נמחק",
     "topic-already-restored": "נושא זה כבר שוחזר",
-    "cant-purge-main-post": "You can't purge the main post, please delete the topic instead",
+    "cant-purge-main-post": "אתה לא יכול למחוק את הפוסט הראשי, אנא מחק את הנושא במקום",
     "topic-thumbnails-are-disabled": "תמונות ממוזערות לנושא אינן מאופשרות.",
     "invalid-file": "קובץ לא תקין",
     "uploads-are-disabled": "העלאת קבצים אינה מאופשרת",
-    "signature-too-long": "Sorry, your signature cannot be longer than %1 character(s).",
+    "signature-too-long": "מצטערים, החתימה שלך לא יכולה להכיל יותר מ-%1 תווים.",
     "about-me-too-long": "מצטערים, דף האודות שלך לא יכול להיות ארוך מ-%1 תווים.",
     "cant-chat-with-yourself": "לא ניתן לעשות צ'אט עם עצמך!",
     "chat-restricted": "משתמש זה חסם את הודעות הצ'אט שלו ממשתמשים זרים. המשתמש חייב לעקוב אחריך לפני שתוכל לשוחח איתו בצ'אט",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "הודעת הצ'אט ארוכה מדי",
     "cant-edit-chat-message": "אתה לא רשאי לערוך הודעה זו",
     "cant-remove-last-user": "אינך יכול למחוק את המשתמש האחרון",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "אתה לא רשאי למחוק הודעה זו",
     "reputation-system-disabled": "מערכת המוניטין לא פעילה.",
     "downvoting-disabled": "היכולת להצביע נגד לא פעילה",
     "not-enough-reputation-to-downvote": "אין לך מספיק מוניטין כדי להוריד את הדירוג של פוסט זה",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "בבקשה השתמש בשם המשתמש שלך להתחברות",
     "invite-maximum-met": "הזמנת את הכמות המירבית של אנשים (%1 מתוך %2).",
     "no-session-found": "לא נמצאו סשני התחברות!",
-    "not-in-room": "User not in room"
+    "not-in-room": "משתמש זה לא בצ'אט",
+    "no-users-in-room": "אין משתמש בחדר הזה",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/he/global.json b/public/language/he/global.json
index f38444f7cc..f4b966429e 100644
--- a/public/language/he/global.json
+++ b/public/language/he/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "הפוסט הועלה ב %1 %2 על ידי %3",
     "user_posted_ago": "%1 העלה פוסט %2",
     "guest_posted_ago": "אורח העלה פוסט %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "נערך לאחרונה על ידי %1",
     "norecentposts": "אין פוסטים מהזמן האחרון",
     "norecenttopics": "אין נושאים מהזמן החרון",
     "recentposts": "פוסטים אחרונים",
@@ -86,5 +86,9 @@
     "delete_all": "מחק הכל",
     "map": "מפה",
     "sessions": "סשני התחברות",
-    "ip_address": "כתובת IP"
+    "ip_address": "כתובת IP",
+    "enter_page_number": "הכנס מספר עמוד",
+    "upload_file": "העלה קובץ",
+    "upload": "העלה",
+    "allowed-file-types": "פורמטי הקבצים המורשים הם %1"
 }
\ No newline at end of file
diff --git a/public/language/he/groups.json b/public/language/he/groups.json
index 31788099a0..fe0bb93380 100644
--- a/public/language/he/groups.json
+++ b/public/language/he/groups.json
@@ -25,7 +25,7 @@
     "details.latest_posts": "פוסטים אחרונים",
     "details.private": "פרטי",
     "details.disableJoinRequests": "בטל בקשות הצטרפות",
-    "details.grant": "Grant/Rescind Ownership",
+    "details.grant": "הענק/בטל בעלות",
     "details.kick": "גרש",
     "details.owner_options": "ניהול הקבוצה",
     "details.group_name": "שם הקבוצה",
@@ -39,8 +39,9 @@
     "details.userTitleEnabled": "הצג סמל",
     "details.private_help": "אם מאופשר, הצטרפות לקבוצות דורשות אישור מבעל הקבוצה",
     "details.hidden": "מוסתר",
-    "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
+    "details.hidden_help": "אם מאופשר, הקבוצה לא תופיע ברשימת הקבוצות, ומשתמשים יצטרכו להיות מוזמנים ידנית",
     "details.delete_group": "מחק קבוצה",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "פרטי הקבוצה עודכנו",
     "event.deleted": "קבוצת \"%1\" נמחקה",
     "membership.accept-invitation": "קבל הזמנה",
@@ -48,5 +49,6 @@
     "membership.join-group": "הצטרף לקבוצה",
     "membership.leave-group": "עזוב קבוצה",
     "membership.reject": "דחה",
-    "new-group.group_name": "שם קבוצה"
+    "new-group.group_name": "שם קבוצה",
+    "upload-group-cover": "העלה תמונת נושא לקבוצה"
 }
\ No newline at end of file
diff --git a/public/language/he/modules.json b/public/language/he/modules.json
index bf5598adf7..6b6f8c40f3 100644
--- a/public/language/he/modules.json
+++ b/public/language/he/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 מקליד ...",
     "chat.user_has_messaged_you": "ל%1 יש הודעה עבורך.",
     "chat.see_all": "צפה בכל הצ'אטים",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "אנא בחר נמען על מנת לראות את היסטוריית הצ'אט איתו",
     "chat.no-users-in-room": "אין משתמשים בחדר הזה",
     "chat.recent-chats": "צ'אטים אחרונים",
@@ -26,7 +27,7 @@
     "composer.user_said": "%1 אמר:",
     "composer.discard": "האם למחוק פוסט זה?",
     "composer.submit_and_lock": "אשר ונעל",
-    "composer.toggle_dropdown": "Toggle Dropdown",
+    "composer.toggle_dropdown": "הדלק/כבה את התפריט הנפתח",
     "composer.uploading": "העלאה %1",
     "bootbox.ok": "בסדר",
     "bootbox.cancel": "בטל",
diff --git a/public/language/he/notifications.json b/public/language/he/notifications.json
index d60e3513d0..30255302fc 100644
--- a/public/language/he/notifications.json
+++ b/public/language/he/notifications.json
@@ -16,20 +16,21 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> ו%2 אחרים הצביעו לפוסט שלך ב<strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> העביר את הפוסט שלך ל<strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> הוזז ל<strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> הוסיף את הפוסט שלך ב <strong>%2</strong> למועדפים שלו.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> ו<strong>%2</strong> הוסיפו את הפוסט שלך למועדפים ב<strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> ו%2 אחרים הוסיפו את הפוסט שלך למועדפים שלהם ב<strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> סימן את הפוסט שלך ב<strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> ו<strong>%2</strong> סימנו את הפוסט שלך ב<strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> ו-%2 אחרים סימנו את הפוסט שלך ב<strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> דיווח על פוסט ב <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> ו<strong>%2</strong> סימנו פוסט ב<strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> ו%2 נוספים סימנו פוסט ב<strong>%3</strong>",
     "user_posted_to": "<strong>%1</strong> פרסם תגובה ל: <strong>%2</strong>",
-    "user_posted_to_dual": "<strong>%1</strong> and <strong>%2</strong> have posted replies to: <strong>%3</strong>",
-    "user_posted_to_multiple": "<strong>%1</strong> and %2 others have posted replies to: <strong>%3</strong>",
+    "user_posted_to_dual": "<strong>%1</strong> ו<strong>%2</strong> הגיבו ל: <strong>%3</strong>",
+    "user_posted_to_multiple": "<strong>%1</strong> ו%2 אחרים הגיבו ל: <strong>%3</strong>",
     "user_posted_topic": "<strong>%1</strong> העלה נושא חדש: <strong>%2</strong>",
     "user_started_following_you": "<strong>%1</strong> התחיל לעקוב אחריך.",
-    "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
-    "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
-    "new_register": "<strong>%1</strong> sent a registration request.",
+    "user_started_following_you_dual": "<strong>%1</strong> ו<strong>%1</strong> התחילו לעקוב אחריך.",
+    "user_started_following_you_multiple": "<strong>%1</strong> ו%2 התחילו לעקוב אחריך.",
+    "new_register": "<strong>%1</strong> שלח בקשת הרשמה.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "כתובת המייל אושרה",
     "email-confirmed-message": "תודה שאישרת את כתובת המייל שלך. החשבון שלך פעיל כעת.",
     "email-confirm-error-message": "אירעה שגיאה בעת אישור המייל שלך. ייתכן כי הקוד היה שגוי או פג תוקף.",
diff --git a/public/language/he/pages.json b/public/language/he/pages.json
index 8344a86f2f..0a36793f6a 100644
--- a/public/language/he/pages.json
+++ b/public/language/he/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "נושאים חמים החודש",
     "popular-alltime": "הנושאים החמים בכל הזמנים",
     "recent": "נושאים אחרונים",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "פוסטים מסומנים",
     "users/online": "משתמשים מחוברים",
     "users/latest": "משתמשים אחרונים",
     "users/sort-posts": "משתמשים עם המונה הגבוה ביותר",
     "users/sort-reputation": "משתמשים עם המוניטין הגבוה ביותר",
-    "users/banned": "Banned Users",
+    "users/banned": "משתמשים מורחקים",
     "users/search": "חיפוש משתמשים",
     "notifications": "התראות",
     "tags": "תגיות",
@@ -33,12 +33,13 @@
     "account/posts": "הודעות שפורסמו על ידי %1",
     "account/topics": "נושאים שנוצרו על ידי %1",
     "account/groups": "הקבוצות של %1",
-    "account/favourites": "הנושאים האהובים על %1",
+    "account/favourites": "הפוסטים שסומנו על ידי %1",
     "account/settings": "הגדרות משתמש",
     "account/watched": "נושאים שנצפו על ידי %1",
     "account/upvoted": "פוסטים שהוצבעו לטובה על ידי %1",
     "account/downvoted": "פוסטים שהוצבעו לרעה על ידי %1",
     "account/best": "הפוסטים הטובים ביותר שנוצרו על ידי %1",
+    "confirm": "כתובת המייל אושרה",
     "maintenance.text": "%1 כרגע תחת עבודות תחזוקה. אנא חזור בזמן מאוחר יותר.",
     "maintenance.messageIntro": "בנוסף, המנהל השאיר את ההודעה הזו:",
     "throttled.text": "%1 לא זמן כעת עקב טעינת יתר. אנא חזור מאוחר יותר."
diff --git a/public/language/he/topic.json b/public/language/he/topic.json
index f68e532e56..49ca0e5bba 100644
--- a/public/language/he/topic.json
+++ b/public/language/he/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "אנא הרשם או התחבר על-מנת לעקוב אחר נושא זה.",
     "markAsUnreadForAll.success": "נושא זה סומן כלא נקרא לכולם.",
     "mark_unread": "סמן כלא נקרא",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "הנושא סומן כלא נקרא.",
     "watch": "עקוב",
     "unwatch": "הפסק לעקוב",
     "watch.title": "קבל התראה כאשר יש תגובות חדשות בנושא זה",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "קטגוריות מבוטלות צבועות באפור",
     "confirm_move": "הזז",
     "confirm_fork": "שכפל",
-    "favourite": "מועדף",
-    "favourites": "מועדפים",
-    "favourites.has_no_favourites": "אין לך כרגע פוסטים מועדפים, סמן מספר פוסטים כמועדפים על מנת לראות אותם כאן!",
+    "favourite": "סימניה",
+    "favourites": "סימניות",
+    "favourites.has_no_favourites": "עוד לא סימנת שום פוסט.",
     "loading_more_posts": "טוען פוסטים נוספים",
     "move_topic": "הזז נושא",
     "move_topics": "הזז נושאים",
@@ -105,7 +105,7 @@
     "stale.warning": "הנושא בו אתה מגיב הוא דיי ישן. האם ברצונך לפתוח נושא חדש, ולהזכיר את הנושא הזה בתגובתך?",
     "stale.create": "צור נושא חדש",
     "stale.reply_anyway": "הגב לנושא זה בכל מקרה",
-    "link_back": "Re: [%1](%2)",
+    "link_back": "תגובה: [%1](%2)",
     "spam": "ספאם",
     "offensive": "פוגעני",
     "custom-flag-reason": "הכנס סיבה מסמנת"
diff --git a/public/language/he/uploads.json b/public/language/he/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/he/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/he/user.json b/public/language/he/user.json
index f63645e7fb..a442d5fd10 100644
--- a/public/language/he/user.json
+++ b/public/language/he/user.json
@@ -22,7 +22,7 @@
     "profile": "פרופיל",
     "profile_views": "צפיות בפרופיל",
     "reputation": "מוניטין",
-    "favourites": "מועדפים",
+    "favourites": "סימניות",
     "watched": "נצפה",
     "followers": "עוקבים",
     "following": "עוקב אחרי",
@@ -39,6 +39,7 @@
     "change_username": "שנה שם משתמש",
     "change_email": "שנה מייל",
     "edit": "ערוך",
+    "edit-profile": "Edit Profile",
     "default_picture": "אייקון ברירת מחדל",
     "uploaded_picture": "התמונה הועלתה",
     "upload_new_picture": "העלה תמונה חדשה",
@@ -55,10 +56,11 @@
     "password": "סיסמה",
     "username_taken_workaround": "שם המשתמש שבחרת כבר תפוס, אז שינינו אותו מעט. שם המשתמש שלך כעת הוא <strong>%1</strong>",
     "password_same_as_username": "הסיסמה שלך זהה לשם המשתמש, אנא בחר סיסמה שונה.",
+    "password_same_as_email": "הסיסמה שלך זהה לכתובת המייל שלך, אנא בחר סיסמה שונה.",
     "upload_picture": "העלה תמונה",
     "upload_a_picture": "העלה תמונה",
     "remove_uploaded_picture": "מחק את התמונה שהועלתה",
-    "image_spec": "עלייך להעלות רק תמונות מפורמט PNG, JPG או BMP",
+    "upload_cover_picture": "העלה תמונת נושא",
     "settings": "הגדרות",
     "show_email": "פרסם את כתובת האימייל שלי",
     "show_fullname": "הצג את שמי המלא",
@@ -71,7 +73,7 @@
     "digest_monthly": "חודשי",
     "send_chat_notifications": "שלח לי הודעה למייל כאשר הודעת צ'אט נשלחה אלי בזמן שאיני מחובר",
     "send_post_notifications": "שלח לי הודעה למייל כאשר תגובות חדשות פורסמו לנושאים שאני עוקב אחריהם",
-    "settings-require-reload": "Some setting changes require a reload. Click here to reload the page.",
+    "settings-require-reload": "כמה שינויים בהגדרות דורשים ריענון לדף. לחץ כאן כדי לרענן את הדף.",
     "has_no_follower": "למשתמש זה אין עוקבים :(",
     "follows_no_one": "משתמש זה אינו עוקב אחרי אחרים :(",
     "has_no_posts": "המשתמש טרם יצר פוסטים כלשהם.",
@@ -82,14 +84,15 @@
     "has_no_voted_posts": "למשתמש אין פוסטים שהוצבעו",
     "email_hidden": "כתובת אימייל מוסתרת",
     "hidden": "מוסתר",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll",
+    "paginate_description": "הצג נושאים ופוסטים כדפים במקום כרשימת גלילה אין-סופית",
     "topics_per_page": "כמות נושאים בעמוד",
     "posts_per_page": "כמות פוסטים בעמוד",
     "notification_sounds": "נגן סאונד כשמתקבלת התראה",
     "browsing": "הגדרות צפייה",
     "open_links_in_new_tab": "פתח קישורים חיצוניים בכרטיסייה חדשה",
     "enable_topic_searching": "הפעל חיפוש בתוך נושא",
-    "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "topic_search_help": "אם מאופשר, החיפוש בתוך הנושא יעקוף את שיטת החיפוש של הדפדפן, ויאפשר לך לחפש בכל הנושא - ולא רק במה שמוצג על המסך",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "עקוב אחרת נושאים שהגבת בהם",
     "follow_topics_you_create": "עקוב אחר נושאים שיצרת",
     "grouptitle": "בחר את כותרת הקבוצה שברצונך להציג",
@@ -98,9 +101,9 @@
     "select-homepage": "בחר דף בית",
     "homepage": "דף הבית",
     "homepage_description": "בחר דף שיהיה דף הבית של הפורום או \"כלום\" על מנת להשתמש בדף הבית ברירת המחדל.",
-    "custom_route": "Custom Homepage Route",
-    "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")",
-    "sso.title": "Single Sign-on Services",
+    "custom_route": "נתיב דף הבית המותאם-אישית",
+    "custom_route_help": "הכנס שם נתיב כאן, ללא לוכסן לפני (לדוגמא \"אחרונים\", או \"פופולארי\")",
+    "sso.title": "שירות יחיד להתחברות",
     "sso.associated": "משוייך עם",
     "sso.not-associated": "לחץ כאן כדי לשייך"
 }
\ No newline at end of file
diff --git a/public/language/he/users.json b/public/language/he/users.json
index 30c141ca8e..7bbdc26e0b 100644
--- a/public/language/he/users.json
+++ b/public/language/he/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "נושאים שלא נקראו",
     "categories": "קטגוריות",
     "tags": "תגיות",
-    "no-users-found": "No users found!"
+    "no-users-found": "לא נמצאו משתמשים!"
 }
\ No newline at end of file
diff --git a/public/language/hu/error.json b/public/language/hu/error.json
index 48bc10ed16..e0744b39ca 100644
--- a/public/language/hu/error.json
+++ b/public/language/hu/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Kitiltott felhasználó",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Nem létező kategória",
     "no-topic": "Nem létező téma",
     "no-post": "Nem létező hozzászólás",
@@ -50,8 +51,8 @@
     "still-uploading": "Kérlek várj, amíg a feltöltés befejeződik.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Már bejelölted Kedvencnek ezt a hozzászólást",
-    "already-unfavourited": "Már kivetted a Kedvenceid közül ezt a hozzászólást",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Nem tilthatsz ki másik adminisztrátort!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Érvénytelen a kép típusa. Engedett kiterjesztések: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Kérlek a felhasználónevedet használd a belépéshez",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/hu/global.json b/public/language/hu/global.json
index ef85232149..63ad09e507 100644
--- a/public/language/hu/global.json
+++ b/public/language/hu/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Összes törlése",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/hu/groups.json b/public/language/hu/groups.json
index d62227498f..0430f698e5 100644
--- a/public/language/hu/groups.json
+++ b/public/language/hu/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json
index 848e1d8fc2..65142c04d3 100644
--- a/public/language/hu/modules.json
+++ b/public/language/hu/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 éppen ír ...",
     "chat.user_has_messaged_you": "%1 üzenetet küldött.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Válasszuk ki a címzettet és tekintsük meg a chat előzményeket",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Legutóbbi beszélgetések",
diff --git a/public/language/hu/notifications.json b/public/language/hu/notifications.json
index a9c8917764..b1b09a5dd7 100644
--- a/public/language/hu/notifications.json
+++ b/public/language/hu/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Confirmed",
     "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
     "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
diff --git a/public/language/hu/pages.json b/public/language/hu/pages.json
index 8bb9535fac..c67ccb5355 100644
--- a/public/language/hu/pages.json
+++ b/public/language/hu/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 jelenleg karbantartás alatt van. Kérlek nézz vissza késöbb!",
     "maintenance.messageIntro": "Ezenkívúl, az adminisztrátor ezt az üzenetet hagyta:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json
index 504121693a..189f065bde 100644
--- a/public/language/hu/topic.json
+++ b/public/language/hu/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Kikapcsolt kategóriák kiszürkülve",
     "confirm_move": "Áthelyezés",
     "confirm_fork": "Szétszedés",
-    "favourite": "Kedvenc",
-    "favourites": "Kedvencek",
-    "favourites.has_no_favourites": "Nincs egyetlen kedvenc hozzászólásod sem, jelölj meg párat hogy itt láthasd őket!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Hozzászólások betöltése",
     "move_topic": "Topik áthelyezése",
     "move_topics": "Move Topics",
diff --git a/public/language/hu/uploads.json b/public/language/hu/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/hu/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/hu/user.json b/public/language/hu/user.json
index 7066698025..fd2a183e03 100644
--- a/public/language/hu/user.json
+++ b/public/language/hu/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Megtekintések",
     "reputation": "Hírnév",
-    "favourites": "Kedvencek",
+    "favourites": "Bookmarks",
     "watched": "Megfigyeli",
     "followers": "Követők",
     "following": "Követve",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Szerkeszt",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Feltöltött kép",
     "upload_new_picture": "Új kép feltöltése",
@@ -55,10 +56,11 @@
     "password": "Jelszó",
     "username_taken_workaround": "A kívánt felhasználónév már foglalt, így változtatnunk kellett rajta egy kicsit. Mostantól <strong>%1</strong> nicknév alatt vagy ismert.",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Kép feltöltése",
     "upload_a_picture": "Egy kép feltöltése",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Beállítások",
     "show_email": "E-mail címem mutatása",
     "show_fullname": "A teljes nevem mutatása",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Témán belüli keresés bekapcsolása",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/id/error.json b/public/language/id/error.json
index 2d10453279..de932ffac2 100644
--- a/public/language/id/error.json
+++ b/public/language/id/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Pengguna dibanned",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategori tidak ditemukan",
     "no-topic": "Topik tidak ditemukan",
     "no-post": "Post tidak ditemukan",
@@ -50,8 +51,8 @@
     "still-uploading": "Tunggu proses upload sampai selesai",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Post ini sudah kamu favorit",
-    "already-unfavourited": "Postingan ini sudah kamu unfavorit",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Kamu tidak dapat ban admin lainnya!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/id/global.json b/public/language/id/global.json
index 7b6b19afa8..c488a606a9 100644
--- a/public/language/id/global.json
+++ b/public/language/id/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Hapus Semua",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/id/groups.json b/public/language/id/groups.json
index 19d5b3d393..32f99e3fdc 100644
--- a/public/language/id/groups.json
+++ b/public/language/id/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/id/modules.json b/public/language/id/modules.json
index c087b260a9..7ddec19b96 100644
--- a/public/language/id/modules.json
+++ b/public/language/id/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 sedang menulis ...",
     "chat.user_has_messaged_you": "%1 telah mengirimkan pesan untukmu.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Mohon pilih satu penerima untuk melihat riwayat pesan percakapan",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Percakapan terbaru",
diff --git a/public/language/id/notifications.json b/public/language/id/notifications.json
index cf4e6165f6..e4290a117d 100644
--- a/public/language/id/notifications.json
+++ b/public/language/id/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> telah memfavoritkan posting mu di <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> menandai sebuah posting di <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> mengirim permintaan registrasi.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email telah Dikonfirmasi",
     "email-confirmed-message": "Terimakasih telah melakukan validasi email. Akunmu saat ini telah aktif sepenuhnya.",
     "email-confirm-error-message": "Terjadi masalah saat melakukan validasi emailmu. Mungkin terjadi kesalahan kode atau waktu habis.",
diff --git a/public/language/id/pages.json b/public/language/id/pages.json
index 3c791e897d..0ec372ded8 100644
--- a/public/language/id/pages.json
+++ b/public/language/id/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 saat ini sedang dalam masa pemeliharaan. Silahkan kembali lain waktu.",
     "maintenance.messageIntro": "Tambahan, Administrator meninggalkan pesan ini:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/id/topic.json b/public/language/id/topic.json
index 8040d55ae4..6ba52cacce 100644
--- a/public/language/id/topic.json
+++ b/public/language/id/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Indikator Kategori yang Ditiadakan keabu-abuan",
     "confirm_move": "Pindah",
     "confirm_fork": "Cabangkan",
-    "favourite": "Favorit",
-    "favourites": "Beberapa Favorit",
-    "favourites.has_no_favourites": "Kamu tidak memiliki favorit, favoritkan beberapa posting untuk melihatnya di sini! ",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Memuat Lebih Banyak Posting",
     "move_topic": "Pindahkan Topik",
     "move_topics": "Pindahkan Beberapa Topik",
diff --git a/public/language/id/uploads.json b/public/language/id/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/id/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/id/user.json b/public/language/id/user.json
index 0a478472f5..4610dd8f43 100644
--- a/public/language/id/user.json
+++ b/public/language/id/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Tampilan profil",
     "reputation": "Reputasi",
-    "favourites": "Favorit",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "Pengikut",
     "following": "Mengikuti",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Perbarui",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Gambar/Foto yang Diunggah",
     "upload_new_picture": "Unggah Gambar/Foto Baru",
@@ -55,10 +56,11 @@
     "password": "Kata Sandi",
     "username_taken_workaround": "Nama pengguna yang kamu inginkan telah diambil, jadi kami merubahnya sedikit. Kamu saat ini dikenal sebagai <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Unggah gambar/foto",
     "upload_a_picture": "Unggah sebuah gambar/foto",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Pengaturan",
     "show_email": "Tampilkan Email Saya",
     "show_fullname": "Tampilkan Nama Lengkap Saya",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Gunakan Pencarian Di dalam Topik",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/it/error.json b/public/language/it/error.json
index f498726867..25358c4310 100644
--- a/public/language/it/error.json
+++ b/public/language/it/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Utente bannato",
     "user-too-new": "Devi attendere %1 secondi prima di creare il tuo primo post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "La Categoria non esiste",
     "no-topic": "Il Topic non esiste",
     "no-post": "Il Post non esiste",
@@ -50,8 +51,8 @@
     "still-uploading": "Per favore attendere il completamento degli uploads.",
     "file-too-big": "La dimensione massima consentita è di %1 kB - si prega di caricare un file più piccolo",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Hai già inserito tra i preferiti questo post",
-    "already-unfavourited": "Hai già inserito tra i Non Preferiti questo post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Non puoi bannare altri amministratori!",
     "cant-remove-last-admin": "Sei l'unico Amministratore. Aggiungi un altro amministratore prima di rimuovere il tuo ruolo",
     "invalid-image-type": "Tipo dell'immagine non valido. I tipi permessi sono: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Per favore usa il tuo nome utente per accedere",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/it/global.json b/public/language/it/global.json
index 722cad0af7..3e19f1688b 100644
--- a/public/language/it/global.json
+++ b/public/language/it/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Elimina Tutto",
     "map": "Mappa",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/it/groups.json b/public/language/it/groups.json
index 4d03c957de..ca2d7f898a 100644
--- a/public/language/it/groups.json
+++ b/public/language/it/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Nascosto",
     "details.hidden_help": "Se abilitato, questo gruppo non sarà visibile nella lista dei gruppi e gli utenti dovranno essere invitati manualmente",
     "details.delete_group": "Elimina il Gruppo",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "I dettagli del Gruppo sono stati aggiornati",
     "event.deleted": "Il gruppo \"%1\" è stato eliminato",
     "membership.accept-invitation": "Accetta l'invito",
@@ -48,5 +49,6 @@
     "membership.join-group": "Entra nel Gruppo",
     "membership.leave-group": "Lascia il Gruppo",
     "membership.reject": "Rifiuta",
-    "new-group.group_name": "Nome Gruppo:"
+    "new-group.group_name": "Nome Gruppo:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/it/modules.json b/public/language/it/modules.json
index 41a6a4b6a9..fd58d80dd1 100644
--- a/public/language/it/modules.json
+++ b/public/language/it/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 sta scrivendo...",
     "chat.user_has_messaged_you": "%1 ti ha scritto.",
     "chat.see_all": "Vedi tutte le chat",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Si prega di selezionare un destinatario per vedere la cronologia dei messaggi",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Chat Recenti",
diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json
index 0980d7b430..fc8f3bccc8 100644
--- a/public/language/it/notifications.json
+++ b/public/language/it/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> ha messo nei favoriti il tuo post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> ha segnalato un post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> ha inviato una richiesta di registrazione.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Confermata",
     "email-confirmed-message": "Grazie per aver validato la tua email. Il tuo account è ora completamente attivato.",
     "email-confirm-error-message": "C'è stato un problema nella validazione del tuo indirizzo email. Potrebbe essere il codice non valido o scaduto.",
diff --git a/public/language/it/pages.json b/public/language/it/pages.json
index 836a6d5181..dd6de7ed92 100644
--- a/public/language/it/pages.json
+++ b/public/language/it/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Post creati da %1",
     "account/topics": "Discussioni create da %1",
     "account/groups": "Gruppi di %1",
-    "account/favourites": "Post Favoriti da %1",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Impostazioni Utente",
     "account/watched": "Discussioni osservate da %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 è attualmente in manutenzione. Per favore ritorna più tardi.",
     "maintenance.messageIntro": "Inoltre, l'amministratore ha lasciato questo messaggio:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/it/topic.json b/public/language/it/topic.json
index 0f7ab7fc1d..8bbcf4c4c2 100644
--- a/public/language/it/topic.json
+++ b/public/language/it/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Le Categorie disabilitate sono in grigio",
     "confirm_move": "Sposta",
     "confirm_fork": "Dividi",
-    "favourite": "Preferito",
-    "favourites": "Preferiti",
-    "favourites.has_no_favourites": "Non hai ancun post preferito; aggiungi qualche post ai preferiti per vederli qui!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Caricamento altri post",
     "move_topic": "Sposta Discussione",
     "move_topics": "Sposta Discussioni",
diff --git a/public/language/it/uploads.json b/public/language/it/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/it/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/it/user.json b/public/language/it/user.json
index ced2e232f1..6202ea0bd8 100644
--- a/public/language/it/user.json
+++ b/public/language/it/user.json
@@ -22,7 +22,7 @@
     "profile": "Profilo",
     "profile_views": "Visite al profilo",
     "reputation": "Reputazione",
-    "favourites": "Favoriti",
+    "favourites": "Bookmarks",
     "watched": "Osservati",
     "followers": "Da chi è seguito",
     "following": "Chi segue",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Modifica",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Foto caricata",
     "upload_new_picture": "Carica una nuova foto",
@@ -55,10 +56,11 @@
     "password": "Password",
     "username_taken_workaround": "Il nome utente che hai richiesto era già stato utilizzato, quindi lo abbiamo modificato leggermente. Ora il tuo è <strong>%1</strong>",
     "password_same_as_username": "La tua password è uguale al tuo username, per piacere scegli un'altra password",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Carica foto",
     "upload_a_picture": "Carica una foto",
     "remove_uploaded_picture": "Elimina foto caricata",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Impostazioni",
     "show_email": "Mostra la mia Email",
     "show_fullname": "Vedi il Mio Nome Completo",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Apri i link web in una nuova pagina",
     "enable_topic_searching": "Abilita la ricerca negli argomenti",
     "topic_search_help": "Se abilitata, la ricerca negli argomenti ignorerà il comportamento predefinito del browser per consentirti di cercare all'interno delle discussioni, anziché soltanto nel contenuto visibile a schermo",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Segui le discussioni a cui hai risposto",
     "follow_topics_you_create": "Segui le discussioni che hai iniziato",
     "grouptitle": "Seleziona il titolo del gruppo che vorresti vedere",
diff --git a/public/language/ja/error.json b/public/language/ja/error.json
index 0fd0029421..7deddf6c53 100644
--- a/public/language/ja/error.json
+++ b/public/language/ja/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "パスワードが長すぎます",
     "user-banned": "ユーザーは停止されています",
     "user-too-new": "申し訳ありません。登録後に投稿を行うには%1秒お待ち下さい。",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "カテゴリは存在しません",
     "no-topic": "トピックは存在しません",
     "no-post": "投稿は存在しません",
@@ -50,8 +51,8 @@
     "still-uploading": "アップロードが完成するまでお待ちください。",
     "file-too-big": "%1kBより大きいサイズファイルが許されません-より小さいファイルをアップして下さい。",
     "guest-upload-disabled": "ゲストさんからのアップを無効にしています",
-    "already-favourited": "あなたはこの投稿をお気に入りにしました",
-    "already-unfavourited": "あなたはこの投稿をお気に入り解除しました",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "ほかの管理者を停止することはできません!",
     "cant-remove-last-admin": "あなたが唯一の管理者です。管理者としてあなた自身を削除する前に、管理者として別のユーザーを追加します。",
     "invalid-image-type": "無効なイメージタイプです。許可された種類は: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/ja/global.json b/public/language/ja/global.json
index 97696d28b6..82cc2ea677 100644
--- a/public/language/ja/global.json
+++ b/public/language/ja/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Delete All",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/ja/groups.json b/public/language/ja/groups.json
index a3793e5a72..d108373457 100644
--- a/public/language/ja/groups.json
+++ b/public/language/ja/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "拒否",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json
index 701f6aa753..204c392bbb 100644
--- a/public/language/ja/modules.json
+++ b/public/language/ja/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 が入力中 ...",
     "chat.user_has_messaged_you": "%1さんからメッセージが届いています。",
     "chat.see_all": "すべてのチャットを見る",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "チャットメッセージの履歴を表示するには、受信者を選択してください",
     "chat.no-users-in-room": "ルームには誰も居ません",
     "chat.recent-chats": "最近のチャット",
diff --git a/public/language/ja/notifications.json b/public/language/ja/notifications.json
index 25cb5e89e6..5fff05542b 100644
--- a/public/language/ja/notifications.json
+++ b/public/language/ja/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Confirmed",
     "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
     "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
diff --git a/public/language/ja/pages.json b/public/language/ja/pages.json
index 63e409692c..717c66327e 100644
--- a/public/language/ja/pages.json
+++ b/public/language/ja/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json
index 746897fdeb..f24579e205 100644
--- a/public/language/ja/topic.json
+++ b/public/language/ja/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "使用不可の板はグレーに表示されます。",
     "confirm_move": "移動",
     "confirm_fork": "フォーク",
-    "favourite": "お気に入り",
-    "favourites": "お気に入り",
-    "favourites.has_no_favourites": "お気に入りはまだありません!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "もっと見る",
     "move_topic": "スレッドを移動",
     "move_topics": "トピックを移動する",
diff --git a/public/language/ja/uploads.json b/public/language/ja/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/ja/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/ja/user.json b/public/language/ja/user.json
index 174930b30c..539809537d 100644
--- a/public/language/ja/user.json
+++ b/public/language/ja/user.json
@@ -22,7 +22,7 @@
     "profile": "プロフィール",
     "profile_views": "閲覧数",
     "reputation": "評価",
-    "favourites": "お気に入り",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "フォロワー",
     "following": "フォロー中",
@@ -39,6 +39,7 @@
     "change_username": "ユーザー名の変更",
     "change_email": "メール変更",
     "edit": "編集",
+    "edit-profile": "Edit Profile",
     "default_picture": "元のアイコン",
     "uploaded_picture": "アップロード済みの画像",
     "upload_new_picture": "新しい画像をアップロード",
@@ -55,10 +56,11 @@
     "password": "パスワード",
     "username_taken_workaround": "このユーザー名はすでに使用されています。いまのユーザー名は <strong>%1</strong> です。",
     "password_same_as_username": "パスワードがユーザー名と同じですから、他のパスワードを使って下さい。",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "画像をアップロード",
     "upload_a_picture": "画像をアップロード",
     "remove_uploaded_picture": "アップした写真を取り消します",
-    "image_spec": "PNG、JPGとBMPのファイルしかアップできません",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "設定",
     "show_email": "メールアドレスを表示",
     "show_fullname": "フルネームを示します",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "外リンクを新しいタブに開きます",
     "enable_topic_searching": "インートピックの検索を有効にします",
     "topic_search_help": "有効にしたら、インートピックの検索はブラウザの既定機能を無視して、スクリーンに示したよりトピック内からの全部を検索します",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "返信したトピックをフォローします",
     "follow_topics_you_create": "投稿したトピックをフォローします",
     "grouptitle": "好きなグループ名を選んで下さい",
diff --git a/public/language/ko/email.json b/public/language/ko/email.json
index 0474c36a09..ff07b4a927 100644
--- a/public/language/ko/email.json
+++ b/public/language/ko/email.json
@@ -21,9 +21,9 @@
     "digest.cta": "%1에 방문하시려면 클릭하세요.",
     "digest.unsub.info": "이 대화는 사용자의 구독 설정에 따라 전송되었습니다.",
     "digest.no_topics": "%1 동안 활성화된 주제가 없습니다.",
-    "digest.day": "day",
-    "digest.week": "week",
-    "digest.month": "month",
+    "digest.day": "일",
+    "digest.week": "주",
+    "digest.month": "월",
     "notif.chat.subject": "%1님이 대화 메시지를 보냈습니다.",
     "notif.chat.cta": "대화를 계속하려면 여기를 클릭하세요.",
     "notif.chat.unsub.info": "이 대화 알림은 사용자의 구독 설정에 따라 전송되었습니다.",
diff --git a/public/language/ko/error.json b/public/language/ko/error.json
index cf908e8447..edb0a3a88d 100644
--- a/public/language/ko/error.json
+++ b/public/language/ko/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "올바르지 않은 비밀번호입니다.",
     "invalid-username-or-password": "사용자 이름과 패스워드를 모두 설정해주세요.",
     "invalid-search-term": "올바르지 않은 검색어입니다.",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "올바르지 않은 값입니다. 최소 1%에서 최대 2%까지 설정해야 합니다.",
     "username-taken": "이미 사용 중인 사용자 이름입니다.",
     "email-taken": "이미 사용 중인 이메일입니다.",
     "email-not-confirmed": "아직 이메일이 인증되지 않았습니다. 여기를 누르면 인증 메일을 발송할 수 있습니다.",
@@ -24,9 +24,10 @@
     "confirm-email-already-sent": "인증 메일이 이미 발송되었습니다. %1 분 이후에 재 발송이 가능합니다.",
     "username-too-short": "사용자 이름이 너무 짧습니다.",
     "username-too-long": "사용자 이름이 너무 깁니다.",
-    "password-too-long": "Password too long",
+    "password-too-long": "패스워드가 너무 깁니다.",
     "user-banned": "차단된 사용자입니다.",
     "user-too-new": "죄송합니다, 첫 번째 게시물은 %1 초 후에 작성할 수 있습니다.",
+    "blacklisted-ip": "죄송하지만, 당신의 IP는 이 커뮤니티로부터 차단되었습니다. 만약 에러라는 생각이 드신다면 관리자에게 연락해주세요.",
     "no-category": "존재하지 않는 카테고리입니다.",
     "no-topic": "존재하지 않는 주제입니다.",
     "no-post": "존재하지 않는 게시물입니다.",
@@ -50,8 +51,8 @@
     "still-uploading": "업로드가 끝날 때까지 기다려주세요.",
     "file-too-big": "업로드 가능한 파일크기는 최대 %1 KB 입니다 - 파일의 용량을 줄이거나 압축을 활용하세요.",
     "guest-upload-disabled": "손님의 파일 업로드는 제한되어 있습니다.",
-    "already-favourited": "이미 이 게시물을 즐겨찾기 하셨습니다.",
-    "already-unfavourited": "이미 이 게시물의 즐겨찾기를 해제하셨습니다.",
+    "already-favourited": "이미 이 게시물을 북마크 했습니다.",
+    "already-unfavourited": "이미 이 게시물을 북마크 해제했습니다.",
     "cant-ban-other-admins": "다른 관리자를 차단할 수 없습니다.",
     "cant-remove-last-admin": "귀하는 유일한 관리자입니다. 관리자를 그만두시기 전에 다른 사용자를 관리자로 선임하세요.",
     "invalid-image-type": "올바르지 않은 이미지입니다. 사용가능한 유형: %1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "메시지가 너무 깁니다.",
     "cant-edit-chat-message": "편집 할 수 있는 권한이 없습니다.",
     "cant-remove-last-user": "마지막 사용자를 삭제할 수 없습니다.",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "메세지를 지울 권한이 없습니다.",
     "reputation-system-disabled": "인지도 기능이 비활성 상태입니다.",
     "downvoting-disabled": "비추천 기능이 비활성 상태입니다.",
     "not-enough-reputation-to-downvote": "인지도가 낮아 이 게시물에 반대할 수 없습니다.",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "사용자명을 통해 로그인하세요.",
     "invite-maximum-met": "초대가능한 사용자를 모두 초대했습니다. (%2명 중 %1을 초대)",
     "no-session-found": "로그인 세션을 찾을 수 없습니다.",
-    "not-in-room": "User not in room"
+    "not-in-room": "없는 사용자입니다.",
+    "no-users-in-room": "사용자가 없습니다.",
+    "cant-kick-self": "스스로 이 그룹을 탈퇴할 수 없습니다."
 }
\ No newline at end of file
diff --git a/public/language/ko/global.json b/public/language/ko/global.json
index b112c196fd..9669750309 100644
--- a/public/language/ko/global.json
+++ b/public/language/ko/global.json
@@ -3,8 +3,8 @@
     "search": "검색",
     "buttons.close": "닫기",
     "403.title": "접근이 거부되었습니다.",
-    "403.message": "You seem to have stumbled upon a page that you do not have access to.",
-    "403.login": "Perhaps you should <a href='%1/login'>try logging in</a>?",
+    "403.message": "접속할 수 없는 페이지에 접근하였습니다.",
+    "403.login": "<a href='/login'>로그인</a>되어 있는지 확인해 주세요.",
     "404.title": "페이지를 찾을 수 없습니다.",
     "404.message": "존재하지 않는 페이지에 접근하였습니다. <a href='%1/'>홈 페이지</a>로 이동합니다.",
     "500.title": "내부 오류가 발생했습니다.",
@@ -49,9 +49,9 @@
     "users": "사용자",
     "topics": "주제",
     "posts": "게시물",
-    "best": "Best",
+    "best": "베스트",
     "upvoted": "Upvoted",
-    "downvoted": "Downvoted",
+    "downvoted": "비추됨",
     "views": "조회 수",
     "reputation": "인기도",
     "read_more": "전체 보기",
@@ -59,19 +59,19 @@
     "posted_ago_by_guest": "익명 사용자가 %1에 작성했습니다.",
     "posted_ago_by": "%2님이 %1에 작성했습니다.",
     "posted_ago": "%1에 작성되었습니다.",
-    "posted_in": "posted in %1",
-    "posted_in_by": "posted in %1 by %2",
+    "posted_in": "%1에 작성되었습니다.",
+    "posted_in_by": "%2님이 %1에 작성했습니다.",
     "posted_in_ago": "%2 %1에 작성되었습니다. ",
     "posted_in_ago_by": "%3님이 %2 %1에 작성했습니다.",
     "user_posted_ago": "%1님이 %2에 작성했습니다.",
     "guest_posted_ago": "익명 사용자가 %1에 작성했습니다.",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "%1님이 마지막으로 수정했습니다.",
     "norecentposts": "최근 작성된 게시물이 없습니다.",
     "norecenttopics": "최근 생성된 생성된 주제가 없습니다.",
     "recentposts": "최근 게시물",
     "recentips": "최근 로그인 IP",
     "away": "자리 비움",
-    "dnd": "Do not disturb",
+    "dnd": "방해 금지",
     "invisible": "오프라인으로 표시",
     "offline": "오프라인",
     "email": "이메일",
@@ -81,10 +81,14 @@
     "updated.title": "포럼이 업데이트 되었습니다.",
     "updated.message": "이 포럼은 지금 최신 버전으로 업데이트 되었습니다. 여기를 누르면 페이지를 새로고침합니다.",
     "privacy": "개인정보",
-    "follow": "Follow",
-    "unfollow": "Unfollow",
-    "delete_all": "Delete All",
-    "map": "Map",
-    "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "follow": "팔로우",
+    "unfollow": "언팔로우",
+    "delete_all": "모두 삭제하기",
+    "map": "맵",
+    "sessions": "로그인 세션",
+    "ip_address": "아이피 주소",
+    "enter_page_number": "페이지 번호를 입력하세요",
+    "upload_file": "파일 업로드",
+    "upload": "업로드",
+    "allowed-file-types": "사용가능한 파일 유형: %1"
 }
\ No newline at end of file
diff --git a/public/language/ko/groups.json b/public/language/ko/groups.json
index 3c521d70c7..18eb6a0b07 100644
--- a/public/language/ko/groups.json
+++ b/public/language/ko/groups.json
@@ -14,7 +14,7 @@
     "invited.search": "그룹에 초대할 사용자를 검색하세요.",
     "invited.notification_title": "<strong>%1</strong> 그룹에 초대되었습니다.",
     "request.notification_title": "<strong>%1</strong> 님으로부터 그룹 구성원 요청이 들어왔습니다.",
-    "request.notification_text": "<strong>%1</strong> has requested to become a member of <strong>%2</strong>",
+    "request.notification_text": "<strong>%1</strong> 님이 <strong>%2</strong> 의 멤버 가입을 신청했습니다.",
     "cover-save": "저장",
     "cover-saving": "저장 중",
     "details.title": "그룹 상세정보",
@@ -24,8 +24,8 @@
     "details.has_no_posts": "이 그룹의 구성원이 작성한 게시물이 없습니다.",
     "details.latest_posts": "최근 게시물",
     "details.private": "비공개",
-    "details.disableJoinRequests": "Disable join requests",
-    "details.grant": "Grant/Rescind Ownership",
+    "details.disableJoinRequests": "가입 신청 비활성화하기",
+    "details.grant": "소유권 이전/포기하기",
     "details.kick": "내보내기",
     "details.owner_options": "그룹 관리",
     "details.group_name": "그룹명",
@@ -41,12 +41,14 @@
     "details.hidden": "숨김",
     "details.hidden_help": "활성 시 그룹 목록에 노출되지 않습니다. 또한 구성원은 초대를 통해서만 가입이 가능합니다.",
     "details.delete_group": "그룹 삭제",
+    "details.private_system_help": "비공개 그룹은 시스템에 의해 비활성화 되었으며, 이 옵션은 아무 기능도 하지 않습니다",
     "event.updated": "그룹 정보가 업데이트 되었습니다.",
     "event.deleted": "%1 그룹이 삭제되었습니다.",
     "membership.accept-invitation": "초대 수락",
-    "membership.invitation-pending": "Invitation Pending",
+    "membership.invitation-pending": "보류중인 초대 수락",
     "membership.join-group": "그룹 들어가기",
     "membership.leave-group": "그룹 나가기",
     "membership.reject": "거절",
-    "new-group.group_name": "그룹명:"
+    "new-group.group_name": "그룹명:",
+    "upload-group-cover": "그룹 커버 업로드"
 }
\ No newline at end of file
diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json
index e9b92a7110..99aea82699 100644
--- a/public/language/ko/modules.json
+++ b/public/language/ko/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1님이 입력 중입니다.",
     "chat.user_has_messaged_you": "%1님이 메시지를 보냈습니다.",
     "chat.see_all": "모든 대화 보기",
+    "chat.mark_all_read": "읽은 채팅 읽음으로 표시",
     "chat.no-messages": "대화 기록을 보려면 대화 상대를 선택하세요.",
     "chat.no-users-in-room": "사용자가 없습니다.",
     "chat.recent-chats": "최근 대화 내용",
@@ -18,7 +19,7 @@
     "chat.three_months": "3개월",
     "chat.delete_message_confirm": "이 대화를 삭제하시겠습니까?",
     "chat.roomname": "%1 대화방 ",
-    "chat.add-users-to-room": "Add users to room",
+    "chat.add-users-to-room": "유저 추가하기",
     "composer.compose": "작성",
     "composer.show_preview": "미리보기",
     "composer.hide_preview": "미리보기 숨김",
@@ -26,7 +27,7 @@
     "composer.user_said": "%1님의 말:",
     "composer.discard": "이 게시물을 지우겠습니까?",
     "composer.submit_and_lock": "잠금 상태로 작성 완료",
-    "composer.toggle_dropdown": "Toggle Dropdown",
+    "composer.toggle_dropdown": "내려서 확인하기",
     "composer.uploading": "%1 업로드 중",
     "bootbox.ok": "확인",
     "bootbox.cancel": "취소",
diff --git a/public/language/ko/notifications.json b/public/language/ko/notifications.json
index 67d76f1766..cf7b14f2b0 100644
--- a/public/language/ko/notifications.json
+++ b/public/language/ko/notifications.json
@@ -5,7 +5,7 @@
     "mark_all_read": "모든 알림을 읽음 상태로 변경",
     "back_to_home": "%1로 돌아가기",
     "outgoing_link": "외부 링크",
-    "outgoing_link_message": "You are now leaving %1",
+    "outgoing_link_message": "%1 에서 나오셨습니다.",
     "continue_to": "%1 사이트로 이동",
     "return_to": "%1 사이트로 돌아가기",
     "new_notification": "새 알림",
@@ -13,23 +13,24 @@
     "new_message_from": "<strong>%1</strong>님이 메시지를 보냈습니다.",
     "upvoted_your_post_in": "<strong>%1</strong>님이 <strong>%2</strong>의 내 게시물을 추천했습니다.",
     "upvoted_your_post_in_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 <strong>%3</strong>의 내 게시물을 추천했습니다.",
-    "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
+    "upvoted_your_post_in_multiple": "<strong>%1</strong> 님과 다른 %2 명이 <strong>%3</strong>의 내 게시물을 추천했습니다.",
     "moved_your_post": "<strong>%1</strong>님이 귀하의 게시물을 <strong>%2</strong>로 옮겼습니다.",
-    "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong>님이 <strong>%2</strong>의 내 게시물을 좋아합니다.",
-    "favourited_your_post_in_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 <strong>%3</strong>을 좋아합니다.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong>님 외 %2명이 <strong>%3</strong>을 좋아합니다.",
+    "moved_your_topic": "<strong>%1</strong> 이 <strong>%2</strong> 로 옮겨졌습니다.",
+    "favourited_your_post_in": "<strong>%1</strong>님이 <strong>%2</strong>의 내 게시물을 북마크 했습니다.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> 님과 <strong>%2</strong> 님이 <strong>%3</strong>의 내 게시물을 북마크 했습니다.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> 님과 다른 %2 명이 <strong>%3</strong>의 내 게시물을 북마크 했습니다.",
     "user_flagged_post_in": "<strong>%1</strong>님이 <strong>%2</strong>의 게시물을 신고했습니다.",
-    "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
-    "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
+    "user_flagged_post_in_dual": "<strong>%1</strong> 님과 <strong>%2</strong> 님이 <strong>%3</strong> 안의 게시물에 플래그를 세웠습니다.",
+    "user_flagged_post_in_multiple": "<strong>%1</strong> 님과 %2 명의 다른 유저들이 <strong>%3</strong> 안의 게시물에 플래그를 세웠습니다.",
     "user_posted_to": "<strong>%1</strong>님이 <strong>%2</strong>에 답글을 작성했습니다.",
-    "user_posted_to_dual": "<strong>%1</strong> and <strong>%2</strong> have posted replies to: <strong>%3</strong>",
-    "user_posted_to_multiple": "<strong>%1</strong> and %2 others have posted replies to: <strong>%3</strong>",
+    "user_posted_to_dual": "<strong>%1</strong> 님과 <strong>%2</strong> 님이 <strong>%3</strong> 에 답글을 달았습니다.",
+    "user_posted_to_multiple": "<strong>%1</strong> 님과 %2 명의 다른 유저들이 <strong>%3</strong> 에 답글을 달았습니다.",
     "user_posted_topic": "<strong>%1</strong>님이 새 주제를 작성했습니다: <strong>%2</strong>",
     "user_started_following_you": "<strong>%1</strong>님이 나를 팔로우합니다.",
     "user_started_following_you_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 당신을 팔로우 시작했습니다.",
     "user_started_following_you_multiple": "<strong>%1</strong>님외 %2명이 당신을 팔로우 시작했습니다.",
     "new_register": "<strong>%1</strong>님이 가입요청을 했습니다.",
+    "new_register_multiple": "<strong>%1<strong> 개의 회원가입 요청이 승인 대기중입니다.",
     "email-confirmed": "확인된 이메일",
     "email-confirmed-message": "이메일을 확인해주셔서 감사합니다. 계정이 완전히 활성화되었습니다.",
     "email-confirm-error-message": "이메일 주소를 검증하지 못했습니다. 코드가 올바르지 않거나 만료되었을 수 있습니다.",
diff --git a/public/language/ko/pages.json b/public/language/ko/pages.json
index 2f9ceae518..65130b5b29 100644
--- a/public/language/ko/pages.json
+++ b/public/language/ko/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "인기있는 주제 (월간)",
     "popular-alltime": "인기있는 주제",
     "recent": "최근 주제",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "플래그된 게시물",
     "users/online": "온라인 사용자",
     "users/latest": "최근 사용자",
     "users/sort-posts": "가장 많은 게시물을 작성한 사용자",
     "users/sort-reputation": "가장 높은 인지도를 가진 사용자",
-    "users/banned": "Banned Users",
+    "users/banned": "차단된 유저",
     "users/search": "사용자 검색",
     "notifications": "알림",
     "tags": "태그",
@@ -33,13 +33,14 @@
     "account/posts": "%1 님이 작성한 게시물",
     "account/topics": "%1 님이 생성한 주제",
     "account/groups": "%1님의 그룹",
-    "account/favourites": "%1님이 좋아하는 게시물",
+    "account/favourites": "%1님의 북마크된 게시물",
     "account/settings": "사용자 설정",
     "account/watched": "%1님이 지켜보는 주제",
-    "account/upvoted": "Posts upvoted by %1",
-    "account/downvoted": "Posts downvoted by %1",
-    "account/best": "Best posts made by %1",
+    "account/upvoted": "%1 님이 upvote한 게시물",
+    "account/downvoted": "%1 님에 의해 Downvote된 게시물",
+    "account/best": "%1 님 최고의 게시물",
+    "confirm": "확인된 이메일",
     "maintenance.text": "%1 사이트는 현재 점검 중입니다. 나중에 다시 방문해주세요.",
     "maintenance.messageIntro": "다음은 관리자가 전하는 메시지입니다.",
-    "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
+    "throttled.text": "과도한 부하로 %1 를 로드할 수 없습니다. 잠시후에 다시 시도해주세요."
 }
\ No newline at end of file
diff --git a/public/language/ko/search.json b/public/language/ko/search.json
index 52dbc78711..611597011a 100644
--- a/public/language/ko/search.json
+++ b/public/language/ko/search.json
@@ -2,7 +2,7 @@
     "results_matching": "\"%2\"와 일치하는 %1개의 결과를 찾았습니다 (검색시간: %3초)",
     "no-matches": "일치하는 결과가 없습니다.",
     "advanced-search": "고급 검색",
-    "in": "In",
+    "in": "안에서 검색",
     "titles": "제목",
     "titles-posts": "제목과 게시물",
     "posted-by": "다음 사용자에 의해 작성됨",
@@ -10,10 +10,10 @@
     "search-child-categories": "하위 카테고리를 검색",
     "reply-count": "답변 수",
     "at-least": "적어도",
-    "at-most": "At most",
+    "at-most": "최대",
     "post-time": "작성시간",
-    "newer-than": "Newer than",
-    "older-than": "Older than",
+    "newer-than": "이전",
+    "older-than": "이후",
     "any-date": "모든 시간",
     "yesterday": "어제",
     "one-week": "일 주",
@@ -22,19 +22,19 @@
     "three-months": "세 달",
     "six-months": "여섯 달",
     "one-year": "일 년",
-    "sort-by": "Sort by",
-    "last-reply-time": "Last reply time",
-    "topic-title": "Topic title",
-    "number-of-replies": "Number of replies",
-    "number-of-views": "Number of views",
-    "topic-start-date": "Topic start date",
+    "sort-by": "정렬",
+    "last-reply-time": "마지막으로 답글이 달린 시간",
+    "topic-title": "주제 제목",
+    "number-of-replies": "답글 수",
+    "number-of-views": "조회 수",
+    "topic-start-date": "주제가 게시된 날짜",
     "username": "사용자명",
     "category": "카테고리",
     "descending": "내림차순",
     "ascending": "오름차순",
     "save-preferences": "설정 저장",
     "clear-preferences": "설정 초기화",
-    "search-preferences-saved": "Search preferences saved",
-    "search-preferences-cleared": "Search preferences cleared",
-    "show-results-as": "Show results as"
+    "search-preferences-saved": "검색 설정이 저장되었습니다.",
+    "search-preferences-cleared": "검색 설정이 초기화되었습니다.",
+    "show-results-as": "검색결과 정렬 - "
 }
\ No newline at end of file
diff --git a/public/language/ko/tags.json b/public/language/ko/tags.json
index 8e1011bd29..d088ef7041 100644
--- a/public/language/ko/tags.json
+++ b/public/language/ko/tags.json
@@ -1,7 +1,7 @@
 {
     "no_tag_topics": "이 태그에 해당하는 주제가 없습니다.",
     "tags": "태그",
-    "enter_tags_here": "Enter tags here, between %1 and %2 characters each.",
+    "enter_tags_here": "%1 와 %2 글자 안에서 태그를 입력하세요.",
     "enter_tags_here_short": "태그 입력...",
     "no_tags": "아직 아무런 태그도 없습니다."
 }
\ No newline at end of file
diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json
index a4110c5839..056ccb5a8f 100644
--- a/public/language/ko/topic.json
+++ b/public/language/ko/topic.json
@@ -26,7 +26,7 @@
     "tools": "도구",
     "flag": "신고",
     "locked": "잠김",
-    "bookmark_instructions": "Click here to return to the last unread post in this thread.",
+    "bookmark_instructions": "여기를 누르면 마지막으로 읽지 않은 포스트로 이동합니다.",
     "flag_title": "이 게시물을 신고",
     "flag_success": "이 게시물은 신고되었습니다.",
     "deleted_message": "이 주제는 삭제되었습니다. 주제 관리 권한이 있는 사용자만 볼 수 있습니다.",
@@ -35,7 +35,7 @@
     "login_to_subscribe": "이 주제의 알림을 받기 위해서는 로그인해야 합니다.",
     "markAsUnreadForAll.success": "모든 사용자에 대해 읽지 않음으로 표시했습니다.",
     "mark_unread": "읽지 않음으로 표시",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "성공적으로 읽지 않음으로 표시했습니다.",
     "watch": "관심 주제",
     "unwatch": "관심 주제 해제",
     "watch.title": "이 주제의 새 답글 알리기",
@@ -51,7 +51,7 @@
     "thread_tools.move_all": "모두 이동",
     "thread_tools.fork": "주제 분리",
     "thread_tools.delete": "삭제",
-    "thread_tools.delete-posts": "Delete Posts",
+    "thread_tools.delete-posts": "게시물 삭제",
     "thread_tools.delete_confirm": "이 주제를 삭제하시겠습니까?",
     "thread_tools.restore": "복원",
     "thread_tools.restore_confirm": "이 주제를 복원하시겠습니까?",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "비활성화된 카테고리는 회색으로 표시됩니다.",
     "confirm_move": "이동",
     "confirm_fork": "분리",
-    "favourite": "좋아요",
-    "favourites": "좋아요",
-    "favourites.has_no_favourites": "좋아하는 게시물이 없습니다.",
+    "favourite": "북마크",
+    "favourites": "북마크",
+    "favourites.has_no_favourites": "북마크한 게시글이 없습니다.",
     "loading_more_posts": "게시물을 로딩 중",
     "move_topic": "주제 이동",
     "move_topics": "주제 이동",
@@ -78,9 +78,9 @@
     "fork_topic_instruction": "분리할 게시물을 선택하세요.",
     "fork_no_pids": "게시물이 선택되지 않았습니다.",
     "fork_success": "주제가 분리되었습니다! 분리된 주제를 보려면 여기를 클릭하세요.",
-    "delete_posts_instruction": "Click the posts you want to delete/purge",
+    "delete_posts_instruction": "삭제할 게시물을 선택하세요.",
     "composer.title_placeholder": "여기에 제목을 입력하세요.",
-    "composer.handle_placeholder": "Name",
+    "composer.handle_placeholder": "이름",
     "composer.discard": "취소",
     "composer.submit": "등록",
     "composer.replying_to": "'%1'에 대한 답글",
@@ -100,12 +100,12 @@
     "oldest_to_newest": "오래된 순으로 정렬",
     "newest_to_oldest": "최신 순으로 정렬",
     "most_votes": "추천수 순으로 정렬",
-    "most_posts": "Most posts",
-    "stale.title": "Create new topic instead?",
-    "stale.warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?",
+    "most_posts": "포스트 많은 순으로 정렬",
+    "stale.title": "새로 주제를 생성하시겠습니까?",
+    "stale.warning": "현재 답글을 작성중인 주제가 꽤 오래되었습니다. 새로 주제를 생성하시고 이글을 인용하시겠습니까?",
     "stale.create": "새로운 주제를 작성",
     "stale.reply_anyway": "아무튼 이 주제에 답변해주세요.",
-    "link_back": "Re: [%1](%2)",
+    "link_back": "답글: [%1](%2)",
     "spam": "스팸",
     "offensive": "공격적인",
     "custom-flag-reason": "신고 사유를 입력하세요."
diff --git a/public/language/ko/uploads.json b/public/language/ko/uploads.json
new file mode 100644
index 0000000000..3438b2ca03
--- /dev/null
+++ b/public/language/ko/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "파일 업로드 중...",
+    "select-file-to-upload": "업로드할 파일을 선택해 주세요!",
+    "upload-success": "파일이 성공적으로 업로드 되었습니다!",
+    "maximum-file-size": "최대 %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/ko/user.json b/public/language/ko/user.json
index ea5c2d9c82..b86ed76b19 100644
--- a/public/language/ko/user.json
+++ b/public/language/ko/user.json
@@ -22,8 +22,8 @@
     "profile": "프로필",
     "profile_views": "프로필 조회 수",
     "reputation": "인기도",
-    "favourites": "좋아요",
-    "watched": "Watched",
+    "favourites": "북마크",
+    "watched": "읽음",
     "followers": "이 사용자를 팔로우",
     "following": "이 사용자가 팔로우",
     "aboutme": "나를 소개",
@@ -39,26 +39,28 @@
     "change_username": "사용자명 변경",
     "change_email": "이메일 변경",
     "edit": "프로필 수정",
+    "edit-profile": "프로필 수정하기",
     "default_picture": "기본 아이콘",
     "uploaded_picture": "사진 업로드",
     "upload_new_picture": "새 사진 업로드",
     "upload_new_picture_from_url": "URL을 통해 새 사진 업로드",
-    "current_password": "현재 패스워드",
-    "change_password": "패스워드 변경",
-    "change_password_error": "올바르지 않은 패스워드",
-    "change_password_error_wrong_current": "현재 패스워드가 올바르지 않습니다.",
-    "change_password_error_length": "패스워드가 너무 짧습니다.",
-    "change_password_error_match": "재입력한 패스워드가 새 패스워드와 일치하지 않습니다!",
-    "change_password_error_privileges": "패스워드를 바꿀 권한이 없습니다.",
-    "change_password_success": "패스워드를 변경했습니다.",
-    "confirm_password": "패스워드 재입력",
-    "password": "패스워드",
+    "current_password": "현재 비밀번호",
+    "change_password": "비밀번호 변경",
+    "change_password_error": "올바르지 않은 비밀번호",
+    "change_password_error_wrong_current": "현재 비밀번호가 올바르지 않습니다.",
+    "change_password_error_length": "비밀번호가 너무 짧습니다.",
+    "change_password_error_match": "재입력한 비밀번호가 새 비밀번호와 일치하지 않습니다!",
+    "change_password_error_privileges": "비밀번호를 바꿀 권한이 없습니다.",
+    "change_password_success": "비밀번호를 변경했습니다.",
+    "confirm_password": "비밀번호 재입력",
+    "password": "비밀번호",
     "username_taken_workaround": "새 사용자 이름이 이미 존재하여 <strong>%1</strong>로 저장되었습니다.",
-    "password_same_as_username": "패스워드가 사용자명과 동일합니다. 다시 입력하세요.",
+    "password_same_as_username": "비밀번호가 사용자명과 동일합니다. 다른 비밀번호를 입력하세요.",
+    "password_same_as_email": "비밀번호가 이메일 주소와 동일합니다. 다른 비밀번호를 입력하세요.",
     "upload_picture": "사진 업로드",
     "upload_a_picture": "사진 업로드",
     "remove_uploaded_picture": "등록된 사진을 삭제",
-    "image_spec": "PNG, JPG 또는 BMP 파일만 업로드 가능합니다.",
+    "upload_cover_picture": "커버 사진 업로드",
     "settings": "설정",
     "show_email": "이메일 공개",
     "show_fullname": "실명 공개",
@@ -70,37 +72,38 @@
     "digest_weekly": "매주",
     "digest_monthly": "매월",
     "send_chat_notifications": "오프라인일 때 채팅 메시지가 도착하면 알림 메일을 보냅니다.",
-    "send_post_notifications": "Send an email when replies are made to topics I am subscribed to",
-    "settings-require-reload": "Some setting changes require a reload. Click here to reload the page.",
+    "send_post_notifications": "내가 구독한 주제에 답글이 달리면 메일 보내기.",
+    "settings-require-reload": "일부 설정 변경은 리로딩이 필요합니다. 여기를 눌러서 페이지를 리로딩 해주세요.",
     "has_no_follower": "아무도 이 사용자를 팔로우하지 않습니다.",
     "follows_no_one": "이 사용자는 아무도 팔로우하지 않습니다.",
     "has_no_posts": "이 사용자가 생성한 게시물이 없습니다.",
     "has_no_topics": "이 사용자가 생성한 주제가 없습니다.",
-    "has_no_watched_topics": "This user hasn't watched any topics yet.",
-    "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.",
-    "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.",
-    "has_no_voted_posts": "This user has no voted posts",
+    "has_no_watched_topics": "이 유저가 관심 목록에 추가한 주제가 없습니다.",
+    "has_no_upvoted_posts": "이 유저가 upvote한 게시물이 없습니다.",
+    "has_no_downvoted_posts": "이 유저가 downvote한 게시물이 없습니다.",
+    "has_no_voted_posts": "이 유저가 vote한 게시물이 없습니다.",
     "email_hidden": "이메일 비공개",
     "hidden": "비공개",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll",
+    "paginate_description": "주제와 게시물을 무한 스크롤 대신 페이지뷰로 보기",
     "topics_per_page": "페이지 당 주제 수",
     "posts_per_page": "페이지 당 게시물 수",
     "notification_sounds": "알림 수신시 소리로 알려주기",
     "browsing": "페이지 열기",
     "open_links_in_new_tab": "외부 링크를 새로운 탭을 사용하여 열람",
     "enable_topic_searching": "주제 내 검색 허용",
-    "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
-    "follow_topics_you_reply_to": "Follow topics that you reply to",
-    "follow_topics_you_create": "Follow topics you create",
-    "grouptitle": "Select the group title you would like to display",
-    "no-group-title": "No group title",
+    "topic_search_help": "활성화 된후 브라우저의 기본 페이지 검색 기능을 연관 주제 검색 기능으로 대신하고 화면에 보여지는 것 뿐만 아니라 주제와 연관된 모든것을 검색합니다.",
+    "scroll_to_my_post": "답글 게시 후 새 포스트 보여주기",
+    "follow_topics_you_reply_to": "답글 단 게시물을 팔로우 합니다.",
+    "follow_topics_you_create": "생성한 주제를 팔로우 합니다.",
+    "grouptitle": "표시할 그룹 이름을 선택하세요.",
+    "no-group-title": "그룹 이름이 없습니다.",
     "select-skin": "스킨 선택",
     "select-homepage": "홈페이지 선택",
     "homepage": "홈페이지",
-    "homepage_description": "Select a page to use as the forum homepage or 'None' to use the default homepage.",
+    "homepage_description": "포럼 홈페이지로 사용할 페이지를 선택하거나 'None'으로 설정하여 기본 홈페이지를 사용합니다.",
     "custom_route": "홈페이지 수동 경로",
-    "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")",
-    "sso.title": "Single Sign-on Services",
-    "sso.associated": "Associated with",
-    "sso.not-associated": "Click here to associate with"
+    "custom_route_help": "라우팅 이름을 앞쪽 '/' 없이 입력해주세요 (예: \"최근 목록\", \"인기 게시물\")",
+    "sso.title": "통합 인증 서비스",
+    "sso.associated": "연관짓기 - ",
+    "sso.not-associated": "이 곳을 클리하여 연관지으세요."
 }
\ No newline at end of file
diff --git a/public/language/ko/users.json b/public/language/ko/users.json
index 4436acc08d..a3ba794ca2 100644
--- a/public/language/ko/users.json
+++ b/public/language/ko/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "읽지 않은 주제",
     "categories": "카테고리",
     "tags": "태그",
-    "no-users-found": "No users found!"
+    "no-users-found": "유저를 찾을 수 없습니다!"
 }
\ No newline at end of file
diff --git a/public/language/lt/error.json b/public/language/lt/error.json
index b124eff563..e23088894e 100644
--- a/public/language/lt/error.json
+++ b/public/language/lt/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Vartotojas užblokuotas",
     "user-too-new": "Atsiprašome, jūs įpareigoti palaukti %1 sekunde(s) prieš rašant pirmą pranešimą",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Tokios kategorijos nėra",
     "no-topic": "Tokios temos nėra",
     "no-post": "Tokio įrašo nėra",
@@ -50,8 +51,8 @@
     "still-uploading": "Prašome palaukti kol bus baigti visi kėlimai į serverį",
     "file-too-big": "Didžiausias įkelimo dydis yra %1 kB - prašome kelti mažesni failą",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Jums jau patinka šis pranešimas",
-    "already-unfavourited": "Jums jau nebepatinka šis pranešimas",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Jūs negalite užblokuoti kitų administratorių!",
     "cant-remove-last-admin": "Jūs esate vienintelis administratorius. Pridėkite kitą vartotoja kaip administratorių prieš pašalindamas save",
     "invalid-image-type": "Neteisingas vaizdo tipas. Leidžiami tipai :%1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Prisijungimui prašome naudoti vartotojo vardą",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/lt/global.json b/public/language/lt/global.json
index aca3f94cfe..5ecfc4b4ab 100644
--- a/public/language/lt/global.json
+++ b/public/language/lt/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Viską ištrinti",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/lt/groups.json b/public/language/lt/groups.json
index 5c34f449e8..cbb1b4118f 100644
--- a/public/language/lt/groups.json
+++ b/public/language/lt/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Paslėptas",
     "details.hidden_help": "Jeigu įjungta, ši grupė bus nerodo grupių sąraše, ir vartotojus reikės kviest rankiniu būdu",
     "details.delete_group": "Ištrinti grupe",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Grupės informacija atnaujinta",
     "event.deleted": "Grupė \"%1\" pašalinta",
     "membership.accept-invitation": "Priimti Kvietimą",
@@ -48,5 +49,6 @@
     "membership.join-group": "Prisijungti Prie Grupės",
     "membership.leave-group": "Palikti Grupę",
     "membership.reject": "Atšaukti",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json
index 241dffe7af..252dd3bfa3 100644
--- a/public/language/lt/modules.json
+++ b/public/language/lt/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 dabar rašo...",
     "chat.user_has_messaged_you": "%1 parašė jums.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Prašome pasirikti gavėją, norėdami peržiūrėti žinučių istoriją",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Paskutiniai susirašinėjimai",
diff --git a/public/language/lt/notifications.json b/public/language/lt/notifications.json
index 16f475d739..640f9bde08 100644
--- a/public/language/lt/notifications.json
+++ b/public/language/lt/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> patinka jūsų pranešimas čia <strong>%2</strong>",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong>pagrįso nuomone čia <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> atsiuntė registracijos prašymą",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "El. paštas patvirtintas",
     "email-confirmed-message": "Dėkojame už el. pašto patvirtinimą. Jūsų paskyra pilnai aktyvuota.",
     "email-confirm-error-message": "Įvyko klaida mėginant patvirtinti Jūsų el. pašto adresą. Galbūt kodas yra neteisingas, arba nebegalioajantis.",
diff --git a/public/language/lt/pages.json b/public/language/lt/pages.json
index 7e87980eb1..9eb817a26c 100644
--- a/public/language/lt/pages.json
+++ b/public/language/lt/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 dabar atnaujinimo darbuose. Prašome grįžti vėliau",
     "maintenance.messageIntro": "Be to, administratorius paliko šį pranešimą:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json
index 2972a90118..a12bdd4c4e 100644
--- a/public/language/lt/topic.json
+++ b/public/language/lt/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Neaktyvios kategorijos pažymėtos pilkai",
     "confirm_move": "Perkelti",
     "confirm_fork": "Išskaidyti",
-    "favourite": "Mėgsta",
-    "favourites": "Mėgstamiausi",
-    "favourites.has_no_favourites": "Neturite jokių mėgiamų pranešimų. Įtraukite pranešimą į mėgiamų sąrašą, kad pamatytumėte juos čia!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Įkeliama daugiau įrašų",
     "move_topic": "Perkelti temą",
     "move_topics": "Perkelti temas",
diff --git a/public/language/lt/uploads.json b/public/language/lt/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/lt/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/lt/user.json b/public/language/lt/user.json
index 6c139be63f..13f3b36b7b 100644
--- a/public/language/lt/user.json
+++ b/public/language/lt/user.json
@@ -22,7 +22,7 @@
     "profile": "Profilis",
     "profile_views": "Profilio peržiūros",
     "reputation": "Reputacija",
-    "favourites": "Mėgstamiausi",
+    "favourites": "Bookmarks",
     "watched": "Peržiūrėjo",
     "followers": "Sekėjai",
     "following": "Seka",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Redaguoti",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Įkeltas paveikslėlis",
     "upload_new_picture": "Įkelti naują paveikslėlį",
@@ -55,10 +56,11 @@
     "password": "Slaptažodis",
     "username_taken_workaround": "Jūsų norimas vartotojo vardas jau užimtas, todėl mes jį šiek tiek pakeitėme. Dabar jūs esate žinomas kaip <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Įkelti paveikslėlį",
     "upload_a_picture": "Įkelti paveikslėlį",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Nustatymai",
     "show_email": "Rodyti mano el. paštą viešai",
     "show_fullname": "Rodyti mano vardą ir pavardę",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Atidaryti išeinančias nuorodas naujam skirtuke",
     "enable_topic_searching": "Įjungti Temų Ieškojimą ",
     "topic_search_help": "Jeigu įjungtas, temų ieškojimas, nepaisys naršyklės puslapio ieškojimo, ir pradės ieškoti tik toje temoje kuri bus rodoma ekrane",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Sekti tas temas kur atrašai tu",
     "follow_topics_you_create": "Sekti tas temas kurias sukuri tu",
     "grouptitle": "Pasirinkite grupės pavadinimą kurį norėtumėte kad atvaizduotu",
diff --git a/public/language/ms/error.json b/public/language/ms/error.json
index 176782b812..7840393a92 100644
--- a/public/language/ms/error.json
+++ b/public/language/ms/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Kata laluan salah!",
     "invalid-username-or-password": "Sila tentukan kedua-dua nama pengguna dan kata laluan",
     "invalid-search-term": "Terma pencarian tak sah",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Nombor halaman tidak sah, mesti tidak kurang dari %1 dan tidak lebih dari %2",
     "username-taken": "Nama pengguna telah digunakan",
     "email-taken": "Emel telah digunakan",
     "email-not-confirmed": "Emel anda belum disahkan lagi, sila klik sini untuk mengesahkan emel anda.",
@@ -24,9 +24,10 @@
     "confirm-email-already-sent": "Pengesahan emel telah dihantar, sila tunggu %1 minit() untuk menghantar yang baru.",
     "username-too-short": "Nama pengunna terlalu pendek",
     "username-too-long": "Nama pengunna terlalu panjang",
-    "password-too-long": "Password too long",
+    "password-too-long": "Kata laluan terlalu panjang",
     "user-banned": "Pengguna diharamkan",
     "user-too-new": "Maaf, anda dikehendaki menunggu %1 saat() sebelum membuat kiriman pertama anda",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategori tidak wujud",
     "no-topic": "Topik tidak wujud",
     "no-post": "Kiriman tidak wujud",
@@ -49,9 +50,9 @@
     "too-many-tags": "Tag terlalu banyak. Topik tidak boleh lebih %1 tag()",
     "still-uploading": "Sila tunggu muatnaik untuk siap.",
     "file-too-big": "Maksimum saiz fail yang dibenarkan ialah %1 kB - sila muatnaik fail yang lebih kecil",
-    "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Anda telah pun menggemari kiriman ini",
-    "already-unfavourited": "Anda telah pun nyah-gemar kiriman ini",
+    "guest-upload-disabled": "Tetamu tidak dibenarkan memuatnaik fail",
+    "already-favourited": "Anda telah pun meletakkan penanda pada kiriman ini",
+    "already-unfavourited": "Anda telah pun membuang penanda untuk kiriman ini",
     "cant-ban-other-admins": "Anda tidak boleh haramkan admin / pentadbir!",
     "cant-remove-last-admin": "Anda satu-satunya pentadbir. Tambah pentadbir lain sebelum membuang diri anda sebagai pentadbir",
     "invalid-image-type": "Jenis imej tak sah. Jenis yang dibenarkan ialah: %1",
@@ -77,13 +78,13 @@
     "about-me-too-long": "Maaf, penerangan tentang anda tidak boleh lebih %1 aksara().",
     "cant-chat-with-yourself": "Anda tidak boleh sembang dengan diri sendiri!",
     "chat-restricted": "Pengguna ini menyekat ruangan sembangnya. Dia hendaklah mengikut anda sebelum kalian dapat bersembang",
-    "chat-disabled": "Chat system disabled",
+    "chat-disabled": "Sistem borak tidak diaktifkan",
     "too-many-messages": "Anda menghantar terlalu banyak pesanan, sila tunggu seketika.",
     "invalid-chat-message": "Mesej borak tidak sah",
     "chat-message-too-long": "Mesej borak terlalu panjang",
-    "cant-edit-chat-message": "You are not allowed to edit this message",
-    "cant-remove-last-user": "You can't remove the last user",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-edit-chat-message": "Anda tidak dibenarkan menyunting mesej ini",
+    "cant-remove-last-user": "Anda tidak boleh membuang pengguna akhir",
+    "cant-delete-chat-message": "Anda tidak dibenarkan memadamkan mesej ini",
     "reputation-system-disabled": "Sistem reputasi dilumpuhkan.",
     "downvoting-disabled": "Undi turun dilumpuhkan",
     "not-enough-reputation-to-downvote": "Anda tidak mempunyai reputasi mencukupi untuk mengundi turun kiriman ini",
@@ -94,7 +95,9 @@
     "parse-error": "Sesuatu tidak kena berlaku ketika menghuraikan repson pelayan (server)",
     "wrong-login-type-email": "Sila guna emel anda untuk log masuk",
     "wrong-login-type-username": "Sila guna nama pengguna anda untuk log masuk",
-    "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
-    "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "invite-maximum-met": "Anda telah menjemput semaksima jumlah orang (%1 daripada %2).",
+    "no-session-found": "Tiada sesyen log masuk dijumpai",
+    "not-in-room": "Pengguna tiada dalam bilik",
+    "no-users-in-room": "Tiada pengguna dalam bilik ini",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/ms/global.json b/public/language/ms/global.json
index 866e0f17f2..4e71e55dfa 100644
--- a/public/language/ms/global.json
+++ b/public/language/ms/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Padam Semua",
     "map": "Peta",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/ms/groups.json b/public/language/ms/groups.json
index 876b702f74..3c84b65c91 100644
--- a/public/language/ms/groups.json
+++ b/public/language/ms/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Sembunyi",
     "details.hidden_help": "Jika dibolehkan, kumpulan ini tidak akan dijumpai di senarai kumpulan, dan pengguna hendaklah di jemput secara manual",
     "details.delete_group": "Padam Kumpulan",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Perincian kumpulan telah dikemaskini",
     "event.deleted": "Kumpulan \"%1\" telah dipadam",
     "membership.accept-invitation": "Terima Jemputan",
@@ -48,5 +49,6 @@
     "membership.join-group": "Masuk Kumpulan",
     "membership.leave-group": "Keluar Kumpulan",
     "membership.reject": "Tolak",
-    "new-group.group_name": "Nama Kumpulan:"
+    "new-group.group_name": "Nama Kumpulan:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json
index 10b3edf2e0..996443d174 100644
--- a/public/language/ms/modules.json
+++ b/public/language/ms/modules.json
@@ -6,8 +6,9 @@
     "chat.user_typing": "%1 menaip",
     "chat.user_has_messaged_you": "%1 mesej anda.",
     "chat.see_all": "Lihat semua",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Sila pilih penerima untuk lihat sejarah sembang",
-    "chat.no-users-in-room": "No users in this room",
+    "chat.no-users-in-room": "Tiada pengguna dalam bilik ini",
     "chat.recent-chats": "Sembang Terbaru",
     "chat.contacts": "Hubungi",
     "chat.message-history": "Sejarah Pesanan",
diff --git a/public/language/ms/notifications.json b/public/language/ms/notifications.json
index e0900929f0..dab972b82d 100644
--- a/public/language/ms/notifications.json
+++ b/public/language/ms/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> dan %2 lagi telah menambah undi pada kiriman anda di <strong>%3</strong>. ",
     "moved_your_post": "<strong>%1</strong> telah memindahkan kiriman anda ke <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> telah memindahkan <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> menggemari kiriman and di <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> dan <strong>%2</strong> telah menggemari kiriman anda di <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong>, %2 dan lain-lain telah menggemari kirimian anda pada <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> menanda kiriman anda di <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> dan <strong>%2</strong> telah menanda kiriman anda pada <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> dan %2 lagi telah mendanda kiriman anda pada <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> dan <strong>%2</strong> mula mengikuti anda.",
     "user_started_following_you_multiple": "<strong>%1</strong> dan %2 lagi mula mengikuti anda.",
     "new_register": "<strong>%1</strong> menghantar jemputan pendaftaran.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Emel Disahkan",
     "email-confirmed-message": "Terima kasih kerana mengesahkan emel anda. Akaun anda telah diaktifkan sepenuhnya.",
     "email-confirm-error-message": "Berlaku masalah semasa mengesahkan emel anda. Mungkin kod tidak sah atau tamat tempoh.",
diff --git a/public/language/ms/pages.json b/public/language/ms/pages.json
index 57368ebc06..c215ed4197 100644
--- a/public/language/ms/pages.json
+++ b/public/language/ms/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Topik Popular Bulan Ini",
     "popular-alltime": "Topik Popular Sepanjang Masa",
     "recent": "Topik Baru",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Kiriman Dipalang",
     "users/online": "Pengguna Atas Talian",
     "users/latest": "Pengguna Terkini",
     "users/sort-posts": "Pengguna Mengikut Kiriman Terbanyak",
     "users/sort-reputation": "Pengguna Mengikut Reputasi Terbanyak",
-    "users/banned": "Banned Users",
+    "users/banned": "Pengguna Diharam",
     "users/search": "Carian Pengguna",
     "notifications": "Makluman",
     "tags": "Tag",
@@ -33,12 +33,13 @@
     "account/posts": "Kiriman oleh %1",
     "account/topics": "Topik olej %1",
     "account/groups": "Kumpulan %1",
-    "account/favourites": "Kiriman Kegemaran %1",
+    "account/favourites": "Kiriman Ditanda Oleh %1",
     "account/settings": "Tetapan Pengguna",
     "account/watched": "Topik Diperhati Oleh %1",
-    "account/upvoted": "Posts upvoted by %1",
-    "account/downvoted": "Posts downvoted by %1",
-    "account/best": "Best posts made by %1",
+    "account/upvoted": "Kiriman diundi naik oleh %1",
+    "account/downvoted": "Kiriman dibuang undi oleh %1",
+    "account/best": "Kiriman Terbaik Oleh %1",
+    "confirm": "Emel Telah Disahkan",
     "maintenance.text": "%1 sedang berada dalam mod pembaikpulihan. Sila cuba lagi nanti.",
     "maintenance.messageIntro": "Tambahan, admin meninggalkan mesej ini :",
     "throttled.text": "%1 tiada buat masa ini kerana permintaan yang berlebihan. Sila datang lagi lain kali."
diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json
index 057b3de204..8c12c5a409 100644
--- a/public/language/ms/topic.json
+++ b/public/language/ms/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Kategori yang disekat diwarnakan kelabu",
     "confirm_move": "Pindahkan",
     "confirm_fork": "Salin",
-    "favourite": "Kegemaran",
-    "favourites": "Kegemaran-kegemaran",
-    "favourites.has_no_favourites": "Anda tiada sebarang kegemaran, tandakan kiriman yang digemari untuk dilihat disini",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Memuatkan lagi kiriman",
     "move_topic": "Pindahkan topik",
     "move_topics": "Pindahkan topik-topik",
diff --git a/public/language/ms/uploads.json b/public/language/ms/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/ms/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/ms/user.json b/public/language/ms/user.json
index 469bce6721..39d38cc538 100644
--- a/public/language/ms/user.json
+++ b/public/language/ms/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Paparan Profil",
     "reputation": "Reputasi",
-    "favourites": "Kegemaran",
+    "favourites": "Bookmarks",
     "watched": "Melihat",
     "followers": "Pengikut",
     "following": "Mengikuti",
@@ -39,6 +39,7 @@
     "change_username": "Tukar Nama Pengguna",
     "change_email": "Tukar Email",
     "edit": "Kemaskini",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Muatnaik gambak",
     "upload_new_picture": "Muatnaik gambar baru",
@@ -55,10 +56,11 @@
     "password": "kata laluan",
     "username_taken_workaround": "Nama pengguna yang anda minta telah digunakan oleh orang lain, jadi kami telah mengubahsuaikannya sedikit. Anda kini dikenali sebagai <strong>%1</strong>",
     "password_same_as_username": "Kata laluan anda adalah sama seperti nama pengguna, sila pilih kata laluan yang lain",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Muatnaik gambar",
     "upload_a_picture": "Muatnaik sekeping gambar",
     "remove_uploaded_picture": "Buang Gambar Yang Dimuatnaik",
-    "image_spec": "Anda hanya boleh muatnaik fail PNG, JPG atau BMP sahaja",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Tetapan",
     "show_email": "Tunjukkan emel saya",
     "show_fullname": "Tunjukkan Nama Penuh",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Buka pautan luar di tab yang baru",
     "enable_topic_searching": "Aktifkan Pencarian Dalam-Topik",
     "topic_search_help": "Jika diaktifkan, pencarian dalam-topik akan membatalkan fungsi asal pencarian pelayan dan membenarkan anda untuk mencari seluruh topik, daripada menunjukkan apa yang terdapat pada skrin sahaja",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Ikut topik yang anda balas",
     "follow_topics_you_create": "Ikut topik yang anda buat",
     "grouptitle": "Pilih nama kumpulan yang anda ingin tunjukkan",
diff --git a/public/language/nb/error.json b/public/language/nb/error.json
index 400ac52998..773e406837 100644
--- a/public/language/nb/error.json
+++ b/public/language/nb/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Bruker utestengt",
     "user-too-new": "Beklager, du må vente %1 sekund(er) før du oppretter ditt første innlegg",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategorien eksisterer ikke",
     "no-topic": "Emne eksisterer ikke",
     "no-post": "Innlegg eksisterer ikke",
@@ -50,8 +51,8 @@
     "still-uploading": "Vennligst vent til opplastingene er fullført.",
     "file-too-big": "Største tillatte filstørrelse er %1 kB – vennligst last opp en mindre fil",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Du har allerede favorittmerket dette innlegget",
-    "already-unfavourited": "Du har allerede avfavorisert dette innlegget",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Du kan ikke utestenge andre administratorer!",
     "cant-remove-last-admin": "Du er den eneste administratoren. Legg til en annen bruker som administrator før du fjerner deg selv.",
     "invalid-image-type": "Ugyldig bildetype. Tilatte typer er: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Vennligst benytt brukernavnet ditt for å logge inn",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/nb/global.json b/public/language/nb/global.json
index d2b6a90c7d..6a7e5f4165 100644
--- a/public/language/nb/global.json
+++ b/public/language/nb/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Slett alle",
     "map": "Kart",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/nb/groups.json b/public/language/nb/groups.json
index 7378fdf292..9354de5542 100644
--- a/public/language/nb/groups.json
+++ b/public/language/nb/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Skjult",
     "details.hidden_help": "Hvis aktivert, vil ikke denne gruppen bli funnet i gruppelisten, og brukere må inviteres manuelt",
     "details.delete_group": "Slett gruppe",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Gruppedetaljer har blitt oppdatert",
     "event.deleted": "Gruppen \"%1\" har blitt slettet",
     "membership.accept-invitation": "Aksepter invitasjon",
@@ -48,5 +49,6 @@
     "membership.join-group": "Bli med i gruppe",
     "membership.leave-group": "Forlat gruppe",
     "membership.reject": "Avslå",
-    "new-group.group_name": "Gruppenavn:"
+    "new-group.group_name": "Gruppenavn:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json
index 4427aba467..a25a971028 100644
--- a/public/language/nb/modules.json
+++ b/public/language/nb/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 skriver ...",
     "chat.user_has_messaged_you": "%1 har sendt deg en melding",
     "chat.see_all": "Se alle samtaler",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Vennligst velg en mottaker for å vise chatte-melding historikk",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Nylige chatter",
diff --git a/public/language/nb/notifications.json b/public/language/nb/notifications.json
index 4329e4e661..f532425c00 100644
--- a/public/language/nb/notifications.json
+++ b/public/language/nb/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> har favorittmerket innlegget ditt i <strong>%2</strong>",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> har flagget et innlegg i <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sendte en forespørsel om registrering",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "E-post bekreftet",
     "email-confirmed-message": "Takk for at du har validert din e-post. Kontoen din er nå fullstendig aktivert.",
     "email-confirm-error-message": "Det oppsto et problem under valdiering av din e-post. Koden kan ha vært ugyldig eller ha utløpt.",
diff --git a/public/language/nb/pages.json b/public/language/nb/pages.json
index a586c6d11f..cda5867418 100644
--- a/public/language/nb/pages.json
+++ b/public/language/nb/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Innlegg opprettet av %1",
     "account/topics": "Emner opprettet av %1",
     "account/groups": "%1 sine grupper",
-    "account/favourites": "%1 sine favoritt-innlegg",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Brukerinnstillinger",
     "account/watched": "Innlegg overvåket av %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 er for tiden under vedlikehold. Kom tilbake en annen gang.",
     "maintenance.messageIntro": "I tillegg har administratoren skrevet denne meldingen:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json
index 5a31251834..3e37dc2ba7 100644
--- a/public/language/nb/topic.json
+++ b/public/language/nb/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Deaktiverte kategorier er grået ut",
     "confirm_move": "Flytt",
     "confirm_fork": "Forgren",
-    "favourite": "Favoritt",
-    "favourites": "Favoritter",
-    "favourites.has_no_favourites": "Du har ingen favoritter, marker noen innlegg som favoritt for å se dem her!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Laster flere innlegg",
     "move_topic": "Flytt emne",
     "move_topics": "Flytt emner",
diff --git a/public/language/nb/uploads.json b/public/language/nb/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/nb/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/nb/user.json b/public/language/nb/user.json
index e28bcf8eea..48cc0e0359 100644
--- a/public/language/nb/user.json
+++ b/public/language/nb/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Profilvisninger",
     "reputation": "Rykte",
-    "favourites": "Favoritter",
+    "favourites": "Bookmarks",
     "watched": "Overvåkede",
     "followers": "Følgere",
     "following": "Følger",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Endre",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Opplastet bilde",
     "upload_new_picture": "Last opp nytt bidle",
@@ -55,10 +56,11 @@
     "password": "Passord",
     "username_taken_workaround": "Brukernavnet du ønsket er opptatt, så vi har endret ditt litt. Du er nå kjent som <strong>%1</string>",
     "password_same_as_username": "Ditt passord er det samme som ditt brukernavn, vennligst velg et annet passord.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Last opp bilde",
     "upload_a_picture": "Last opp et bilde",
     "remove_uploaded_picture": "Fjern Opplastet Bilde",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Innstillinger",
     "show_email": "Vis min e-post",
     "show_fullname": "Vis mitt fulle navn",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Åpne utgående lenker i en ny fane",
     "enable_topic_searching": "Aktiver søk-i-emne",
     "topic_search_help": "Hvis søk-i-emne er aktivert, overstyres nettleserens standard sidesøk og gir mulighet til å søke gjennom hele emnet, ikke bare det som vises på skjermen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Følg emner du besvarer",
     "follow_topics_you_create": "Følg emner du oppretter",
     "grouptitle": "Velg gruppetittelen du vil vise",
diff --git a/public/language/nl/category.json b/public/language/nl/category.json
index 78037189b3..c65576b9dd 100644
--- a/public/language/nl/category.json
+++ b/public/language/nl/category.json
@@ -1,12 +1,12 @@
 {
     "category": "Categorie",
-    "subcategories": "subcategorie",
+    "subcategories": "Subcategorieën",
     "new_topic_button": "Nieuw onderwerp",
     "guest-login-post": "Log in om een reactie te plaatsen",
     "no_topics": "<strong>Er zijn geen onderwerpen in deze categorie.</strong><br />Waarom maak je er niet een aan?",
     "browsing": "browsing",
     "no_replies": "Niemand heeft gereageerd",
-    "no_new_posts": "Geen nieuwe berichten",
+    "no_new_posts": "Geen nieuwe berichten.",
     "share_this_category": "Deel deze categorie",
     "watch": "Volgen",
     "ignore": "Negeren",
diff --git a/public/language/nl/email.json b/public/language/nl/email.json
index 8723ae98aa..829ad4fcdc 100644
--- a/public/language/nl/email.json
+++ b/public/language/nl/email.json
@@ -5,26 +5,26 @@
     "greeting_no_name": "Hallo",
     "greeting_with_name": "Hallo %1",
     "welcome.text1": "Bedank voor het registreren bij %1!",
-    "welcome.text2": "Om de account volledig te activeren, dienen de instructies uit het bevestigingsbericht opgevolgd te worden. Controleer daarom nu eerst de e-mail inbox voor de activeringscode en volg de link in het bericht.",
+    "welcome.text2": "Om je account volledig te activeren, moet je de instructies uit het bevestigingsbericht opvolgen. Controleer daarom nu eerst je e-mail inbox voor de activeringscode en volg de link in het bericht.",
     "welcome.text3": "Een administrator heeft uw registratie geaccepteerd. U kan nu inloggen met uw gebruikersnaam en wachtwoord.",
-    "welcome.cta": "Klik hier voor bevestigen van het e-mailadres",
+    "welcome.cta": "Klik hier om je e-mailadres te bevestigen",
     "invitation.text1": "%1 heeft u uitgenodigd voor %2 ",
-    "invitation.ctr": "Klik hier om uw account aan te maken.",
-    "reset.text1": "Wij ontvingen zojuist een verzoek om het wachtwoord van de account, bij onze website bekend als gekoppeld aan dit e-mailadres, te herstellen. Is dit verzoek niet bekend en geautoriseerd, dan kan dit bericht genegeerd worden.",
-    "reset.text2": "Om het wachtwoord opnieuw in te stellen, klik op deze link:",
-    "reset.cta": "Klik hier voor wachtwoordherstel",
+    "invitation.ctr": "Klik hier om je account aan te maken.",
+    "reset.text1": "We hebben een verzoek ontvangen om je wachtwoord te herstellen, wellicht omdat je hem bent vergeten. Indien dit niet het geval is kan je deze e-mail gewoon negeren.",
+    "reset.text2": "Om je wachtwoord opnieuw in te stellen klik je op deze link:",
+    "reset.cta": "Klik hier om je wachtwoord te resetten",
     "reset.notify.subject": "Wachtwoord succesvol gewijzigd",
-    "reset.notify.text1": "Op %1 is het wachtwoord van de bij ons geregistreerde gebruikersaccount succesvol gewijzigd.",
-    "reset.notify.text2": "Neem onmiddellijk contact met een beheerder op wanneer  geen toestemming voor deze actie gegeven is en deze niet vanuit hier aangevraagd is.",
+    "reset.notify.text1": "Op %1 is het wachtwoord van je account succesvol gewijzigd.",
+    "reset.notify.text2": "Neem onmiddellijk contact met een beheerder op wanneer je hiervoor geen toestemming hebt gegeven.",
     "digest.notifications": "Er zijn ongelezen notificaties van %1:",
     "digest.latest_topics": "De meest recente onderwerpen van %1",
     "digest.cta": "Klik hier om deze website te bezoeken %1 ",
-    "digest.unsub.info": "Deze samenvatting is vanwege instellingen voor abonnementen van de gebruikersaccount door ons verzonden.",
-    "digest.no_topics": "Er zijn geen actieve onderwerpen geweest %1",
+    "digest.unsub.info": "Deze samenvatting hebben we naar je verzonden omdat je dat hebt ingesteld.",
+    "digest.no_topics": "In de afgelopen %1 zijn er geen actieve onderwerpen geweest.",
     "digest.day": "dag",
     "digest.week": "week",
     "digest.month": "maand",
-    "notif.chat.subject": "Nieuw chatbericht ontvangen van %1",
+    "notif.chat.subject": "Nieuw chatbericht van %1",
     "notif.chat.cta": "Klik hier om het gesprek te hervatten",
     "notif.chat.unsub.info": "Deze notificatie is verzonden vanwege de gebruikersinstellingen voor abonnementen.",
     "notif.post.cta": "Klik hier om het volledige bericht te lezen",
diff --git a/public/language/nl/error.json b/public/language/nl/error.json
index 6c12e80542..9b6c912a23 100644
--- a/public/language/nl/error.json
+++ b/public/language/nl/error.json
@@ -1,32 +1,33 @@
 {
-    "invalid-data": "Ongeldige Data",
-    "not-logged-in": "Dit account lijkt op dit moment niet ingelogd te zijn.",
+    "invalid-data": "Ongeldige data",
+    "not-logged-in": "Het lijkt erop dat je niet ingelogd bent.",
     "account-locked": "Dit account is tijdelijk vergrendeld",
-    "search-requires-login": "Zoeken vereist een account - gelieve aan te melden of te registreren.",
+    "search-requires-login": "Zoeken vereist een account - meld je aan of registreer je om te zoeken.",
     "invalid-cid": "Ongeldige categoriesleutel",
-    "invalid-tid": "Ongeldig id voor onderwerp",
+    "invalid-tid": "Ongeldig onderwerp ID",
     "invalid-pid": "Ongeldig berichtkenmerk",
     "invalid-uid": "Ongeldig gebruikerskenmerk",
     "invalid-username": "Ongeldige gebruikersnaam",
     "invalid-email": "Ongeldig e-mailadres",
-    "invalid-title": "Ongeldige titel",
+    "invalid-title": "Ongeldige titel!",
     "invalid-user-data": "Ongeldige gebruikersgegevens",
     "invalid-password": "Ongeldig wachtwoord",
     "invalid-username-or-password": "Geef zowel een gebruikersnaam als wachtwoord op",
-    "invalid-search-term": "Ongeldig zoekopdracht, een of meerdere termen",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-search-term": "Ongeldig zoekterm",
+    "invalid-pagination-value": "Invalide paginering waarde. De waarde moet op z'n minst %1 zijn en niet hoger dan %2 zijn.",
     "username-taken": "Gebruikersnaam is al in gebruik ",
-    "email-taken": "E-mailadres is al eens eerder gebruikt",
-    "email-not-confirmed": "Het e-mailadres van dit account is nog niet bevestigd. Klik hier om het e-mailadres te bevestigen en de registratie af te ronden.",
+    "email-taken": "E-mailadres is al in gebruik",
+    "email-not-confirmed": "Het e-mailadres van dit account is nog niet bevestigd, klik hier om je e-mailadres te bevestigen.",
     "email-not-confirmed-chat": "Het gebruik van chatfunctionaliteit is pas toegestaan na validatie van het e-mailadres.",
     "no-email-to-confirm": "Dit berichtenforum vereist bevestiging per e-mail, klik hier om een e-mailadres te registreren",
     "email-confirm-failed": "Helaas kon het e-mailadres niet bevestigd worden, probeer het later nog eens.",
-    "confirm-email-already-sent": "Bevestigingsbericht per e-mail al zojuist verzonden, wacht even een %1 tal minuutjes voordat opnieuw een bericht verzonden wordt.",
-    "username-too-short": "Gebruikersnaam bevat niet voldoende tekens",
-    "username-too-long": "Gebruikersnaam bevat meer dan het toegestane aantal tekens",
-    "password-too-long": "Wachtwoord te lang",
+    "confirm-email-already-sent": "Bevestigingsmail is zojuist al verzonden, wacht alsjeblieft %1 minuut (minuten) voordat je opnieuw een bevestigingsmail aanvraagt.",
+    "username-too-short": "Gebruikersnaam is te kort",
+    "username-too-long": "Gebruikersnaam is te lang",
+    "password-too-long": "Wachtwoord is te lang",
     "user-banned": "Gebruiker verbannen",
     "user-too-new": "Helaas, het is een vereiste om %1 seconde(n) te wachten voordat het eerste bericht geplaatst kan worden.",
+    "blacklisted-ip": "Sorry, uw IP-adres is verbannen uit deze community. Als u meent dat dit onterecht is, neem dan contact op met een beheerder.",
     "no-category": "Categorie bestaat niet",
     "no-topic": "Onderwerp bestaat niet",
     "no-post": "Bericht bestaat niet",
@@ -50,18 +51,18 @@
     "still-uploading": "Een moment geduld tot alle bestanden overgebracht zijn...",
     "file-too-big": "Maximum toegestane bestandsgrootte is %1 kB - probeer een kleiner bestand te verzenden",
     "guest-upload-disabled": "Uploads voor gasten zijn uitgeschaleld ",
-    "already-favourited": "Dit bericht staat al tussen de favorieten",
-    "already-unfavourited": "Dit bericht is al uit favorieten verwijderd",
+    "already-favourited": "Je hebt dit bericht al als favoriet toegevoegd",
+    "already-unfavourited": "Je hebt dit bericht al verwijderd uit je favorieten",
     "cant-ban-other-admins": "Het is niet toegestaan andere beheerders te verbannen!",
-    "cant-remove-last-admin": "U bent de enige administrator. Voeg een andere gebruiker toe als administrator voordat u uw zelf verwijderd als admin",
+    "cant-remove-last-admin": "Je bent de enige beheerder. Stel eerst een andere gebruiker als beheerder in voordat je jezelf geen beheerder meer maakt.",
     "invalid-image-type": "Ongeldig bestandstype afbeelding. Deze afbeelding is van een bestandstype dat niet ondersteund wordt. Toegestane bestandstypes voor afbeeldingsbestanden zijn: %1",
     "invalid-image-extension": "Ongeldig bestandstype afbeelding",
     "invalid-file-type": "Dit bestandstype wordt niet ondersteund. Toegestane bestandstypen zijn: %1",
-    "group-name-too-short": "De groepsnaam bevat niet genoeg tekens",
+    "group-name-too-short": "De groepsnaam is te kort",
     "group-already-exists": "Een groep met deze naam bestaat al",
-    "group-name-change-not-allowed": "Het veranderen van de groepsnaam is niet toegestaan!",
+    "group-name-change-not-allowed": "Het aanpassen van de groepsnaam is niet toegestaan",
     "group-already-member": "Deze gebruiker is al lid van deze groep",
-    "group-not-member": "Deze gebruiker is niet lid van deze groep",
+    "group-not-member": "Deze gebruiker is geen lid van deze groep",
     "group-needs-owner": "De groep vereist ten minste 1 eigenaar",
     "group-already-invited": "Deze gebruiker is al uitgenodigt",
     "group-already-requested": "Uw lidmaatschap aanvraag is al verstuurd",
@@ -78,23 +79,25 @@
     "cant-chat-with-yourself": "Het is niet mogelijk om met jezelf een chatgesprek te houden.",
     "chat-restricted": "Deze gebruiker heeft beperkingen aan de chatfunctie opgelegd waardoor deze eerst iemand moet volgen voordat deze persoon een nieuwe chat mag initiëren.",
     "chat-disabled": "Chat systeem uitgeschakeld",
-    "too-many-messages": "Er zijn in korte tijd teveel berichten verzonden, een moment geduld.",
+    "too-many-messages": "Je hebt in korte tijd veel berichten verstuurd, als je even wacht mag je weer berichten sturen.",
     "invalid-chat-message": "Ongeldig bericht",
     "chat-message-too-long": "Het chatbericht is te lang",
     "cant-edit-chat-message": "Het is niet toegestaan om dit bericht aan te passen",
     "cant-remove-last-user": "Je kan de laatste gebruiker niet verwijderen",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Het is niet toegestaan om dit bericht te verwijderen",
     "reputation-system-disabled": "Reputatie systeem is uitgeschakeld.",
-    "downvoting-disabled": "Negatief stemmen staat uitgeschakeld.",
-    "not-enough-reputation-to-downvote": "Dit gebruikersaccount beschikt over onvoldoende reputatie om een negatieve stem uit te mogen brengen.",
-    "not-enough-reputation-to-flag": "Onvoldoende reputatie om dit bericht aan beheerders te mogen melden.",
-    "already-flagged": "U heeft deze post al gerapporteerd",
-    "reload-failed": "Tijdens het herladen van \"%1\" is NodeBB een fout of probleem tegen gekomen. NodeBB blijft operationeel. Echter het is verstandig om de oorzaak te onderzoeken en wellicht de vorige actie, voor het herladen, ongedaan te maken.",
+    "downvoting-disabled": "Negatief stemmen is uitgeschakeld",
+    "not-enough-reputation-to-downvote": "Je hebt onvoldoende reputatie om een negatieve stem uit te mogen brengen",
+    "not-enough-reputation-to-flag": "Je hebt onvoldoende reputatie om dit bericht aan de beheerders te mogen melden",
+    "already-flagged": "Je hebt deze post al gerapporteerd",
+    "reload-failed": "Tijdens het herladen van \"%1\" is NodeBB een fout of probleem tegengekomen. NodeBB blijft operationeel. Echter het is verstandig om de oorzaak te onderzoeken en wellicht de vorige actie, voor het herladen, ongedaan te maken.",
     "registration-error": "Fout tijdens registratie",
     "parse-error": "Tijdens het verwerken van het antwoord van de server is er iets misgegaan.",
-    "wrong-login-type-email": "Gebruik het e-mailadres voor aanmelden",
-    "wrong-login-type-username": "Geef de gebruikersnaam voor aanmelden",
+    "wrong-login-type-email": "Gebruik je e-mailadres om in te loggen",
+    "wrong-login-type-username": "Gebruik je gebruikersnaam om in te loggen",
     "invite-maximum-met": "Je heb het maximum aantal mensen uitgenodigd (%1 van de %2).",
     "no-session-found": "Geen login sessie gevonden!",
-    "not-in-room": "User not in room"
+    "not-in-room": "Gebruiker niet in de chat",
+    "no-users-in-room": "Er zijn geen gebruikers in deze chat",
+    "cant-kick-self": "Je kunt jezelf niet uit een groep schoppen"
 }
\ No newline at end of file
diff --git a/public/language/nl/global.json b/public/language/nl/global.json
index 4940d479ec..d1dc94a360 100644
--- a/public/language/nl/global.json
+++ b/public/language/nl/global.json
@@ -2,9 +2,9 @@
     "home": "Home",
     "search": "Zoeken",
     "buttons.close": "Sluiten",
-    "403.title": "Geen toegang",
-    "403.message": "Deze account heeft onvoldoende systeemrechten om toegang tot de pagina te krijgen.",
-    "403.login": "Wellicht proberen <a href='%1/login'>aan te melden</a>?",
+    "403.title": "Toegang geweigerd",
+    "403.message": "Het lijkt erop dat je op een pagina beland bent waar je geen toegang tot hebt.",
+    "403.login": "Je kan proberen <a href='%1/login'>in te loggen</a>?",
     "404.title": "Niet gevonden",
     "404.message": "Deze pagina bestaat niet. Klik hier om naar de <a href='%1/'>hoofdpagina</a> van deze website te navigeren.",
     "500.title": "Interne fout.",
@@ -12,8 +12,8 @@
     "register": "Registeren",
     "login": "Login",
     "please_log_in": "Aanmelden",
-    "logout": "Afmelden",
-    "posting_restriction_info": "Momenteel mogen alleen geregistreerde leden reageren. Klik om naar de aanmeldpagina te gaan.",
+    "logout": "Uitloggen",
+    "posting_restriction_info": "Reageren is momenteel beperkt tot geregistreerde leden, klik hier om in te loggen.",
     "welcome_back": "Welkom terug",
     "you_have_successfully_logged_in": "Aanmelden succesvol",
     "save_changes": "Wijzigingen opslaan",
@@ -42,7 +42,7 @@
     "alert.success": "Succes",
     "alert.error": "Fout",
     "alert.banned": "Verbannen",
-    "alert.banned.message": "Deze account is verbannen en wordt automatisch afgemeld.",
+    "alert.banned.message": "Je bent verbannen en wordt automatisch uitgelogd.",
     "alert.unfollow": "%1 wordt niet langer gevolgd!",
     "alert.follow": "%1 wordt nu gevolgd!",
     "online": "Online",
@@ -65,16 +65,16 @@
     "posted_in_ago_by": "geplaatst in %1 %2 door %3",
     "user_posted_ago": "%1 plaatste %2",
     "guest_posted_ago": "Gast plaatste %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "voor het laatst aangepast door %1",
     "norecentposts": "Geen recente berichten",
     "norecenttopics": "Geen recente onderwerpen",
     "recentposts": "Recente berichten",
     "recentips": "IP-adressen van recente gebruikers",
     "away": "Afwezig",
-    "dnd": "Niet Storen",
+    "dnd": "Niet storen",
     "invisible": "Onzichtbaar",
     "offline": "Offline",
-    "email": "E-mailadres",
+    "email": "E-mail",
     "language": "Taal",
     "guest": "Gast",
     "guests": "Gasten",
@@ -85,6 +85,10 @@
     "unfollow": "Ontvolgen",
     "delete_all": "Alles verwijderen",
     "map": "Kaart",
-    "sessions": "Login Sessies",
-    "ip_address": "IP Adres"
+    "sessions": "Login sessies",
+    "ip_address": "IP Adres",
+    "enter_page_number": "Voer paginanummer in",
+    "upload_file": "Upload bestand",
+    "upload": "Upload",
+    "allowed-file-types": "Toegestane bestandstypen zijn %1"
 }
\ No newline at end of file
diff --git a/public/language/nl/groups.json b/public/language/nl/groups.json
index 7e914a227b..2bf421bd5b 100644
--- a/public/language/nl/groups.json
+++ b/public/language/nl/groups.json
@@ -1,9 +1,9 @@
 {
     "groups": "Groepen",
-    "view_group": "Weergeven groep",
+    "view_group": "Bekijk groep",
     "owner": "Groepseigenaar",
-    "new_group": "Nieuwe groep",
-    "no_groups_found": "Geen groepen voor weergave",
+    "new_group": "Nieuwe groep aanmaken",
+    "no_groups_found": "Er zijn geen groepen om weer te geven",
     "pending.accept": "Accepteer",
     "pending.reject": "Afwijzen",
     "pending.accept_all": "Iedereen accepteren",
@@ -41,6 +41,7 @@
     "details.hidden": "Niet getoond",
     "details.hidden_help": "Indien geactiveerd zal deze groep niet getoond worden in de groepslijst en zullen gebruikers handmatig uitgenodigd moeten worden.",
     "details.delete_group": "Groep verwijderen",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Groepsdetails zijn bijgewerkt",
     "event.deleted": "De groep \"%1\" is verwijderd",
     "membership.accept-invitation": "Uitnodiging accepteren",
@@ -48,5 +49,6 @@
     "membership.join-group": "Deelnemen aan groep",
     "membership.leave-group": "Verlaat groep",
     "membership.reject": "Afwijzen",
-    "new-group.group_name": "Groepsnaam:"
+    "new-group.group_name": "Groepsnaam:",
+    "upload-group-cover": "Upload groepscover"
 }
\ No newline at end of file
diff --git a/public/language/nl/login.json b/public/language/nl/login.json
index 0e2a65a11a..9b84acdc8a 100644
--- a/public/language/nl/login.json
+++ b/public/language/nl/login.json
@@ -1,11 +1,11 @@
 {
     "username-email": "Gebruikersnaam / Email",
     "username": "Gebruikersnaam",
-    "email": "Email",
+    "email": "E-mail",
     "remember_me": "Aangemeld blijven?",
     "forgot_password": "Wachtwoord vergeten?",
     "alternative_logins": "Andere manieren van aanmelden",
     "failed_login_attempt": "Aanmelden niet geslaagd. Probeer het nog eens.",
-    "login_successful": "Aanmelden geslaagd!",
+    "login_successful": "Je bent succesvol ingelogd!",
     "dont_have_account": "Geen gebruikersaccount?"
 }
\ No newline at end of file
diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json
index e9649e27a2..dd7de3cc62 100644
--- a/public/language/nl/modules.json
+++ b/public/language/nl/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 is aan het typen ...",
     "chat.user_has_messaged_you": "%1 heeft een bericht gestuurd",
     "chat.see_all": "Laat alle chats zien",
+    "chat.mark_all_read": "Markeer alle chats als gelezen",
     "chat.no-messages": "Selecteer een ontvanger om de chatgeschiedenis in te zien",
     "chat.no-users-in-room": "Geen gebruikers in deze chat room",
     "chat.recent-chats": "Recent gevoerde gesprekken",
@@ -16,7 +17,7 @@
     "chat.seven_days": "7 dagen",
     "chat.thirty_days": "30 dagen",
     "chat.three_months": "3 maanden",
-    "chat.delete_message_confirm": "Weet u het zeker dat u dit bericht wilt verwijderen?",
+    "chat.delete_message_confirm": "Weet je zeker dat je dit bericht wilt verwijderen?",
     "chat.roomname": "Chat Room %1",
     "chat.add-users-to-room": "Voeg gebruikers toe aan deze chat room",
     "composer.compose": "Samenstellen",
@@ -25,11 +26,11 @@
     "composer.user_said_in": "%1 zegt in %2:",
     "composer.user_said": "%1 zegt:",
     "composer.discard": "Bericht plaatsen annuleren?",
-    "composer.submit_and_lock": "Plaats een bericht en vergrendel direct het onderwerp",
+    "composer.submit_and_lock": "Bericht plaatsen en sluiten",
     "composer.toggle_dropdown": "Keuzelijst schakelen",
     "composer.uploading": "Uploaden van %1",
     "bootbox.ok": "OK",
-    "bootbox.cancel": "Anuleren",
+    "bootbox.cancel": "Annuleren",
     "bootbox.confirm": "Bevestig",
     "cover.dragging_title": "Omslag Foto Positionering",
     "cover.dragging_message": "Sleep de omslag foto voor de gewilde positie en klik \"Opslaan\"",
diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json
index a047b2fad8..a3917e238e 100644
--- a/public/language/nl/notifications.json
+++ b/public/language/nl/notifications.json
@@ -8,7 +8,7 @@
     "outgoing_link_message": "U verlaat nu %1",
     "continue_to": "Door naar %1",
     "return_to": "Terug naar %1",
-    "new_notification": "Nieuwe melding",
+    "new_notification": "Nieuwe notificatie",
     "you_have_unread_notifications": "Je hebt nieuwe notificaties.",
     "new_message_from": "Nieuw bericht van <strong>%1</strong>",
     "upvoted_your_post_in": "<strong>%1</strong> heeft voor een bericht gestemd in <strong>%2</strong>.",
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> en %2 andere hebben in gestemd in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> heeft je bericht verplaatst naar <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> heeft <strong>%2</strong> verplaatst",
-    "favourited_your_post_in": "<strong>%1</strong> heeft een van je berichten in <strong>%2</strong> aan zijn of haar favorieten toegevoegd.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> en <strong>%2</strong> hebben een van je berichten in <strong>%3</strong> aan zijn of haar favorieten toegevoegd.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> en %2 hebben een van je berichten in <strong>3</strong> aan hun favorieten toegevoegd.",
+    "favourited_your_post_in": "<strong>%1</strong> heeft je bericht in <strong>%2</strong> aan zijn/haar favorieten toegevoegd.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> en <strong>%2</strong> hebben je bericht in <strong>%3</strong> aan hun favorieten toegevoegd.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> en %2 anderen hebben je bericht in <strong>%3</strong> aan hun favorieten toegevoegd.",
     "user_flagged_post_in": "<strong>%1</strong> rapporteerde een bericht in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> en <strong>%2</strong> rapporteerde een bericht in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> en %2 andere rapporteede een bericht in <strong>%3</strong>",
@@ -30,8 +30,9 @@
     "user_started_following_you_dual": "<strong>%1</strong> en <strong>%2</strong> volgen jou nu.",
     "user_started_following_you_multiple": "<strong%1>%1</strong> en %2 andere volgen jou nu.",
     "new_register": "<strong>%1</strong> heeft een registratie verzoek aangevraagd.",
+    "new_register_multiple": "Er is/zijn <strong>%1</strong> registratieverzoek(en) die wacht(en) op goedkeuring.",
     "email-confirmed": "E-mailadres bevestigd",
-    "email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Dit account is nu volledig geactiveerd.",
+    "email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Je account is nu volledig geactiveerd.",
     "email-confirm-error-message": "Er was een probleem met het bevestigen van dit e-mailadres. Misschien is de code niet goed ingevoerd of was de beschikbare tijd inmiddels verstreken.",
     "email-confirm-sent": "Bevestigingsmail verstuurd."
 }
\ No newline at end of file
diff --git a/public/language/nl/pages.json b/public/language/nl/pages.json
index 1e709dec0e..96f5f11a38 100644
--- a/public/language/nl/pages.json
+++ b/public/language/nl/pages.json
@@ -1,17 +1,17 @@
 {
     "home": "Home",
     "unread": "Ongelezen onderwerpen",
-    "popular-day": "De populaire onderwerpen van vandaag",
+    "popular-day": "Populaire onderwerpen vandaag",
     "popular-week": "De populaire onderwerpen van deze week",
     "popular-month": "De populaire onderwerpen van deze maand",
     "popular-alltime": "De populaire onderwerpen",
     "recent": "Recente onderwerpen",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Ongepaste berichten",
     "users/online": "Online Gebruikers",
     "users/latest": "Meest recente gebruikers",
     "users/sort-posts": "Gebruikers met de meeste berichten",
     "users/sort-reputation": "Gebruikers met de meeste reputatie",
-    "users/banned": "Banned Users",
+    "users/banned": "Verbannen Gebruikers",
     "users/search": "Zoek Gebruiker",
     "notifications": "Notificaties",
     "tags": "Tags",
@@ -33,12 +33,13 @@
     "account/posts": "Berichten geplaatst door %1",
     "account/topics": "Onderwerpen begonnen door %1",
     "account/groups": "%1's groepen",
-    "account/favourites": "Favoriete berichten van %1",
+    "account/favourites": "%1's Favoriete Berichten",
     "account/settings": "Gebruikersinstellingen",
     "account/watched": "Berichten die door %1 bekeken worden",
     "account/upvoted": "Berichten omhoog gestemd door %1",
     "account/downvoted": "Berichten omlaag gestemd door %1",
     "account/best": "Beste berichten geplaast door %1",
+    "confirm": "Email Bevestigd",
     "maintenance.text": "%1 is momenteel in onderhoud. Excuses voor het ongemak en probeer het later nog eens.",
     "maintenance.messageIntro": "Daarnaast heeft de beheerder het volgende bericht achtergelaten:",
     "throttled.text": "%1 is momenteel niet beschikbaar door overmatig gebruikt. Excuses voor het ongemak en probeer het later nog eens."
diff --git a/public/language/nl/register.json b/public/language/nl/register.json
index ed4b13a411..70d1f69af3 100644
--- a/public/language/nl/register.json
+++ b/public/language/nl/register.json
@@ -1,10 +1,10 @@
 {
     "register": "Registreren",
-    "help.email": "Je email is standaard verborgen voor andere gebruikers.",
+    "help.email": "E-mailadressen zijn standaard verborgen voor andere gebruikers.",
     "help.username_restrictions": "Een unieke gebruikersnaam tussen %1 en %2 karakters. Anderen kunnen je vermelden met @<span id='yourUsername'>gebruikersnaam</span>.",
     "help.minimum_password_length": "Je wachtwoord moet tenminste %1 karakters lang zijn.",
-    "email_address": "Email Adres",
-    "email_address_placeholder": "Vul Email Adres in",
+    "email_address": "E-mailadres",
+    "email_address_placeholder": "Vul e-mailadres in",
     "username": "Gebruikersnaam",
     "username_placeholder": "Vul Gebruikersnaam in",
     "password": "Wachtwoord",
@@ -12,8 +12,8 @@
     "confirm_password": "Bevestig Wachtwoord",
     "confirm_password_placeholder": "Bevestig Wachtwoord",
     "register_now_button": "Nu Registreren",
-    "alternative_registration": "Alternatieve Registratie",
+    "alternative_registration": "Alternatieve registratie",
     "terms_of_use": "Gebruiksvoorwaarden",
-    "agree_to_terms_of_use": "Ik ga akkoord van de Gebruiksvoorwaarden",
-    "registration-added-to-queue": "Uw registratie is toegevoegd aan de wachtrij. U krijgt een email wanneer uw registratie is geaccepteerd "
+    "agree_to_terms_of_use": "Ik ga akkoord met de gebruiksvoorwaarden",
+    "registration-added-to-queue": "Het registratieverzoek is toegevoegd aan de wachtrij. Een bericht wordt naar het opgegeven emailadres gestuurd wanneer de registratie is goedgekeurd."
 }
\ No newline at end of file
diff --git a/public/language/nl/reset_password.json b/public/language/nl/reset_password.json
index 9849338c56..c5b9ef1a9f 100644
--- a/public/language/nl/reset_password.json
+++ b/public/language/nl/reset_password.json
@@ -2,7 +2,7 @@
     "reset_password": "Wachtwoord opnieuw instellen",
     "update_password": "Wachtwoord bijwerken",
     "password_changed.title": "Wachtwoord gewijzigd",
-    "password_changed.message": "<p>Wachtwoord met succes hersteld. Log nu eerst <a href=\"/login\">opnieuw in</a>.",
+    "password_changed.message": "<p>Wachtwoord is met succes hersteld. <a href=\"/login\">Opnieuw inloggen</a>.",
     "wrong_reset_code.title": "Onjuiste herstelcode",
     "wrong_reset_code.message": "Opgegeven code voor wachtwoordherstel is niet juist. Probeer het opnieuw of <a href=\"/reset\">vraag een andere code aan</a>.",
     "new_password": "Nieuw wachtwoord",
@@ -12,6 +12,6 @@
     "password_reset_sent": "Het bericht met daarin een link en de vervolgstappen voor het wachtwoordherstel, is verzonden",
     "invalid_email": "Onbekend e-mailadres!",
     "password_too_short": "Het opgegeven wachtwoord bevat te weinig tekens. Kies een veiliger wachtwoord met meer tekens.",
-    "passwords_do_not_match": "De twee opgegeven wachtwoorden komen niet overeen`",
+    "passwords_do_not_match": "De twee opgegeven wachtwoorden komen niet overeen",
     "password_expired": "Het huidige wachtwoord is verlopen en er dient een nieuwe gekozen te worden"
 }
\ No newline at end of file
diff --git a/public/language/nl/search.json b/public/language/nl/search.json
index da9e793e8b..5e6d54ffee 100644
--- a/public/language/nl/search.json
+++ b/public/language/nl/search.json
@@ -1,7 +1,7 @@
 {
     "results_matching": "%1 overeenkomstige resultaten \"%2\", (%3 seconds)",
     "no-matches": "Geen overeenkomstige resultaten gevonden",
-    "advanced-search": "Geavanceerde zoekfunctie",
+    "advanced-search": "Geavanceerd zoeken",
     "in": "in",
     "titles": "Titels",
     "titles-posts": "Titels en berichten",
diff --git a/public/language/nl/success.json b/public/language/nl/success.json
index be632b25e4..638e1bdcf4 100644
--- a/public/language/nl/success.json
+++ b/public/language/nl/success.json
@@ -1,6 +1,6 @@
 {
     "success": "Geslaagd",
-    "topic-post": "Bericht succesvol geplaatst",
+    "topic-post": "Je bericht is met succes geplaatst.",
     "authentication-successful": "Aanmelden geslaagd",
     "settings-saved": "Instellingen opgeslagen!"
 }
\ No newline at end of file
diff --git a/public/language/nl/tags.json b/public/language/nl/tags.json
index f4665e87c3..318f1870e1 100644
--- a/public/language/nl/tags.json
+++ b/public/language/nl/tags.json
@@ -1,5 +1,5 @@
 {
-    "no_tag_topics": "Er zijn geen onderwerpen met deze tag",
+    "no_tag_topics": "Er zijn geen onderwerpen met deze tag.",
     "tags": "Tags",
     "enter_tags_here": "Voeg hier tags toe, tussen de %1 en %2 tekens per stuk.",
     "enter_tags_here_short": "Voer tags in...",
diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json
index c1202c9e9e..90f6f2be90 100644
--- a/public/language/nl/topic.json
+++ b/public/language/nl/topic.json
@@ -1,7 +1,7 @@
 {
     "topic": "Onderwerp",
     "topic_id": "Onderwerp ID",
-    "topic_id_placeholder": "Vul Onderwerp ID in",
+    "topic_id_placeholder": "Vul onderwerp ID in",
     "no_topics_found": "Geen onderwerpen gevonden!",
     "no_posts_found": "Geen berichten gevonden!",
     "post_is_deleted": "Dit bericht is verwijderd!",
@@ -26,16 +26,16 @@
     "tools": "Extra",
     "flag": "Markeren",
     "locked": "Gesloten",
-    "bookmark_instructions": "Klik hier om terug te gaan om de nieuwste bericht te bekijken in deze onderwerp",
+    "bookmark_instructions": "Klik hier om naar het nieuwste ongelezen bericht te gaan.",
     "flag_title": "Bericht aan beheerders melden",
-    "flag_success": "Het bericht is gerapporteerd aan beheer.",
+    "flag_success": "Dit bericht is gerapporteerd aan de beheerder.",
     "deleted_message": "Dit onderwerp is verwijderd. Alleen gebruikers met beheerrechten op onderwerpniveau kunnen dit inzien.",
     "following_topic.message": "Vanaf nu worden meldingen ontvangen zodra iemand een reactie op dit onderwerp geeft.",
-    "not_following_topic.message": "U ontvangt geen notificaties over dit onderwerp.",
+    "not_following_topic.message": "Je ontvangt geen notificaties meer over dit onderwerp.",
     "login_to_subscribe": "Log in or registreer om dit onderwerp te volgen.",
-    "markAsUnreadForAll.success": "Onderwerp is voor iedereen als 'gelezen' gemarkeerd.",
+    "markAsUnreadForAll.success": "Onderwerp is voor iedereen als ongelezen gemarkeerd.",
     "mark_unread": "Ongelezen markeren",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Onderwerp is als ongelezen gemarkeerd.",
     "watch": "Volgen",
     "unwatch": "Niet meer volgen",
     "watch.title": "Krijg meldingen van nieuwe reacties op dit onderwerp",
@@ -45,7 +45,7 @@
     "thread_tools.markAsUnreadForAll": "Ongelezen markeren",
     "thread_tools.pin": "Onderwerp vastpinnen",
     "thread_tools.unpin": "Onderwerp losmaken",
-    "thread_tools.lock": "Onderwerp op slot zetten",
+    "thread_tools.lock": "Onderwerp sluiten",
     "thread_tools.unlock": "Onderwerp openen",
     "thread_tools.move": "Onderwerp verplaatsen",
     "thread_tools.move_all": "Verplaats alles",
@@ -56,7 +56,7 @@
     "thread_tools.restore": "Onderwerp herstellen",
     "thread_tools.restore_confirm": "Zeker weten dit onderwerp te herstellen?",
     "thread_tools.purge": "Wis onderwerp ",
-    "thread_tools.purge_confirm": "Is het echt de bedoeling dit onderwerp definitief te wissen?",
+    "thread_tools.purge_confirm": "Weet je zeker dat je dit onderwerp wil verwijderen?",
     "topic_move_success": "Verplaatsen van onderwerp naar %1 succesvol",
     "post_delete_confirm": "Is het absoluut de bedoeling dit bericht te verwijderen?",
     "post_restore_confirm": "Is het de bedoeling dit bericht te herstellen?",
@@ -67,8 +67,8 @@
     "confirm_fork": "Splits",
     "favourite": "Favoriet",
     "favourites": "Favorieten",
-    "favourites.has_no_favourites": "Er zijn momenteel nog geen favorieten, markeer eerst enkele berichten om ze hier te kunnen zien.",
-    "loading_more_posts": "Meer berichten...",
+    "favourites.has_no_favourites": "Je hebt nog geen berichten aan je favorieten toegevoegd.",
+    "loading_more_posts": "Meer berichten laden...",
     "move_topic": "Onderwerp verplaatsen",
     "move_topics": "Verplaats onderwerpen",
     "move_post": "Bericht verplaatsen",
diff --git a/public/language/nl/unread.json b/public/language/nl/unread.json
index 3624cddce6..4a5fc11558 100644
--- a/public/language/nl/unread.json
+++ b/public/language/nl/unread.json
@@ -1,7 +1,7 @@
 {
     "title": "Ongelezen",
-    "no_unread_topics": "Er zijn geen ongelezen onderwerpen",
-    "load_more": "Meer laden...",
+    "no_unread_topics": "Er zijn geen ongelezen onderwerpen.",
+    "load_more": "Meer laden",
     "mark_as_read": "Markeer als gelezen",
     "selected": "Geselecteerd",
     "all": "Alles",
diff --git a/public/language/nl/uploads.json b/public/language/nl/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/nl/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/nl/user.json b/public/language/nl/user.json
index b19040a6a2..70d646156b 100644
--- a/public/language/nl/user.json
+++ b/public/language/nl/user.json
@@ -6,9 +6,9 @@
     "postcount": "Aantal geplaatste berichten",
     "email": "E-mail",
     "confirm_email": "Bevestig e-mail",
-    "ban_account": "Verban Account",
+    "ban_account": "Verban gebruiker",
     "ban_account_confirm": "Weet u zeker dat u deze gebruiker wilt verbannen?",
-    "unban_account": "Unban Account",
+    "unban_account": "Verbanning intrekken",
     "delete_account": "Account verwijderen",
     "delete_account_confirm": "Controleer of dat het zeker is dat deze account verwijderd moet worden. <br /><strong> Deze actie kan niet ongedaan  gemaakt worden en herstellen van gebruiker- of profielgegevens is niet mogelijk</strong><br /><br /> Typ hier de gebruikersnaam als extra controle om te bevestigen dat deze account verwijderd moet worden.",
     "delete_this_account_confirm": "Weet u zeker dat u deze account wilt verwijderen? <br /><strong>Deze actie kan niet ongedaan worden en u kunt niet de informatie herstellen</strong><br /><br />",
@@ -39,6 +39,7 @@
     "change_username": "Wijzig gebruikersnaam",
     "change_email": "Wijzig email",
     "edit": "Bewerken",
+    "edit-profile": "Profiel wijzigen",
     "default_picture": "Standaard icoon",
     "uploaded_picture": "Geüploade afbeelding",
     "upload_new_picture": "Nieuwe afbeelding opsturen",
@@ -55,12 +56,13 @@
     "password": "Wachtwoord",
     "username_taken_workaround": "Helaas, de gewenste gebruikersnaam is al door iemand in gebruik genomen dus vandaar een kleine aanpassing naar <strong>%1</strong> doorgevoerd",
     "password_same_as_username": "Je wachtwoord is hetzelfde als je gebruikersnaam. Kies een ander wachtwoord.",
+    "password_same_as_email": "Je wachtwoord is hetzelfde als je email, kies alsjeblieft een ander wachtwoord.",
     "upload_picture": "Upload afbeelding",
     "upload_a_picture": "Upload een afbeelding",
     "remove_uploaded_picture": "Verwijder gëuploade foto",
-    "image_spec": "U mag alleen PNG, JPG of BMP bestanden uploaden.",
+    "upload_cover_picture": "Upload je coverafbeelding",
     "settings": "Instellingen",
-    "show_email": "Inschakelen weergave van e-mailadres op profielpagina",
+    "show_email": "E-mailadres weergeven",
     "show_fullname": "Laat mijn volledige naam zien",
     "restrict_chats": "Sta alleen chatsessies toe van gebruikers die ik zelf volg",
     "digest_label": "Abonneer op een samenvatting",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open uitgaande links naar een externe site in een nieuw tabblad",
     "enable_topic_searching": "Inschakelen mogelijkheid op onderwerp te kunnen zoeken",
     "topic_search_help": "Wanneer ingeschakeld zal de standaard zoekfunctie, met een aangepaste methode voor onderwerpen, overschreven worden",
+    "scroll_to_my_post": "Toon het nieuwe bericht na het plaatsen van een antwoord",
     "follow_topics_you_reply_to": "Volg de onderwerpen waarop ik gereageerd heb",
     "follow_topics_you_create": "Volg de onderwerpen waarvan ik de oorspronkelijke auteur ben",
     "grouptitle": "Selecteer de groepstitel voor weergave",
@@ -98,7 +101,7 @@
     "select-homepage": "Selecteer een startpagina",
     "homepage": "Startpagina",
     "homepage_description": "Selecteer een pagina om te gebruiken als startpagina, of selecteer geen om de standaard pagina te gebruiken.",
-    "custom_route": "Startpagina Route",
+    "custom_route": "Aangepaste startpagina route",
     "custom_route_help": "Voer een route naam hier, zonder enige voorafgaande schuine streep (zoals, \"recente\" of \"populaire\")",
     "sso.title": "Single Sign-on Services",
     "sso.associated": "Geassocieerd met",
diff --git a/public/language/nl/users.json b/public/language/nl/users.json
index a00d246ab7..03dcb21bbb 100644
--- a/public/language/nl/users.json
+++ b/public/language/nl/users.json
@@ -1,5 +1,5 @@
 {
-    "latest_users": "Meest recente gebruikers",
+    "latest_users": "Laatste gebruikers",
     "top_posters": "Meest actieve leden",
     "most_reputation": "Meeste reputatie",
     "search": "Zoeken",
@@ -16,5 +16,5 @@
     "unread_topics": "Ongelezen onderwerpen",
     "categories": "Categorieën",
     "tags": "Tags",
-    "no-users-found": "No users found!"
+    "no-users-found": "Geen gebruikers gevonden!"
 }
\ No newline at end of file
diff --git a/public/language/pl/error.json b/public/language/pl/error.json
index 1e7438c4a2..196a9e6c77 100644
--- a/public/language/pl/error.json
+++ b/public/language/pl/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Użytkownik zbanowany",
     "user-too-new": "Przepraszamy, musisz odczekać %1 sekund(y) przed utworzeniem pierwszego posta",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategoria nie istnieje",
     "no-topic": "Temat nie istnieje",
     "no-post": "Post nie istnieje",
@@ -50,8 +51,8 @@
     "still-uploading": "Poczekaj na pełne załadowanie",
     "file-too-big": "Maksymalny dopuszczalny rozmiar pliku to %1kB - prosimy przesłać mniejszy plik",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Już polubiłeś ten post",
-    "already-unfavourited": "Już przestałeś lubić ten post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Nie możesz zbanować innych adminów!",
     "cant-remove-last-admin": "Jesteś jedynym administratorem. Dodaj innego użytkownika jako administratora przed usunięciem siebie z tej grupy",
     "invalid-image-type": "Błędny typ obrazka. Dozwolone typy to: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Zaloguj się używając nazwy użytkownika",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/pl/global.json b/public/language/pl/global.json
index 03bd0ac844..2aba9da79d 100644
--- a/public/language/pl/global.json
+++ b/public/language/pl/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Usuń wszystko",
     "map": "Mapa",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/pl/groups.json b/public/language/pl/groups.json
index 52e28b2263..845409bb43 100644
--- a/public/language/pl/groups.json
+++ b/public/language/pl/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Ukryty",
     "details.hidden_help": "Jeśli aktywowane, ta grupa nie będzie widoczna w wykazie grup, a użytkownicy będą musieli być zapraszani manualnie.",
     "details.delete_group": "Usuń Grupę",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Dane grupy zostały zaktualizowane",
     "event.deleted": "Grupa \"%1\" została skasowana",
     "membership.accept-invitation": "Przyjmij Zaproszenie",
@@ -48,5 +49,6 @@
     "membership.join-group": "Dołącz Do Grupy",
     "membership.leave-group": "Opuść Grupę",
     "membership.reject": "Odrzuć",
-    "new-group.group_name": "Nazwa Grupy:"
+    "new-group.group_name": "Nazwa Grupy:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json
index 4b5f8e4134..ea0d3c2c10 100644
--- a/public/language/pl/modules.json
+++ b/public/language/pl/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 pisze...",
     "chat.user_has_messaged_you": "%1 napisał do Ciebie",
     "chat.see_all": "Zobacz wszystkie rozmowy",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Wybierz odbiorcę, by wyświetlić historię rozmów.",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Ostatnie rozmowy",
diff --git a/public/language/pl/notifications.json b/public/language/pl/notifications.json
index 75ea1cff24..5873b93a47 100644
--- a/public/language/pl/notifications.json
+++ b/public/language/pl/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1<strong> polubił Twój post w <strong>%2<strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1<strong> oflagował Twój post w <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> wysłał żądanie rejestracji.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "E-mail potwierdzony",
     "email-confirmed-message": "Dziękujemy za potwierdzenie maila. Twoje konto zostało aktywowane.",
     "email-confirm-error-message": "Wystąpił problem przy aktywacji, - kod jest błędny lub przestarzały",
diff --git a/public/language/pl/pages.json b/public/language/pl/pages.json
index a2550c5a43..a9def8e4aa 100644
--- a/public/language/pl/pages.json
+++ b/public/language/pl/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posty napisane przez %1",
     "account/topics": "Tematy stworzone przez %1",
     "account/groups": "Grupy %1",
-    "account/favourites": "Ulubione posty %1",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Ustawienia Użytkownika",
     "account/watched": "Tematy obserwowane przez %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "Obecnie trwają prace konserwacyjne nad %1. Proszę wrócić później.",
     "maintenance.messageIntro": "Dodatkowo, administrator zostawił wiadomość:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json
index 6280c7ddd0..974a61dd94 100644
--- a/public/language/pl/topic.json
+++ b/public/language/pl/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Zablokowane kategorie zostały wyszarzone.",
     "confirm_move": "Przenieś",
     "confirm_fork": "Skopiuj",
-    "favourite": "Polub",
-    "favourites": "Ulubione",
-    "favourites.has_no_favourites": "Nie masz żadnych ulubionych. Polub kilka postów!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Załaduj więcej postów",
     "move_topic": "Przenieś Temat",
     "move_topics": "Przenieś Tematy",
diff --git a/public/language/pl/uploads.json b/public/language/pl/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/pl/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/pl/user.json b/public/language/pl/user.json
index 1b36470c6a..a5f79daab8 100644
--- a/public/language/pl/user.json
+++ b/public/language/pl/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "wyświetleń",
     "reputation": "reputacji",
-    "favourites": "Ulubione",
+    "favourites": "Bookmarks",
     "watched": "Obserwowane",
     "followers": "Obserwujących",
     "following": "Obserwowanych",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Edytuj",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Przesłane zdjęcie",
     "upload_new_picture": "Prześlij nowe zdjęcie",
@@ -55,10 +56,11 @@
     "password": "Hasło",
     "username_taken_workaround": "Wybrany login jest już zajęty, więc zmieniliśmy go trochę. Proponujemy  <strong>%1</strong>",
     "password_same_as_username": "Twoje hasło jest takie samo jak nazwa użytkownika, prosimy wybrać inne hasło.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Prześlij zdjęcie",
     "upload_a_picture": "Prześlij zdjęcie",
     "remove_uploaded_picture": "Usuń Przesłane Zdjęcie",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Ustawienia",
     "show_email": "Wyświetlaj mój adres e-mail",
     "show_fullname": "Wyświetlaj moją pełną nazwę",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Otwieraj linki wychodzące w nowej karcie",
     "enable_topic_searching": "Odblokuj szukanie w temacie",
     "topic_search_help": "Jeśli włączone, wyszukiwanie w tematach zastąpi przeglądarkowe przeszukiwanie strony i pozwoli na przeszukanie całego tematu, zamiast ograniczonej zawartości aktualnie wyświetlonej na ekranie",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Śledź tematy, na które odpowiadasz",
     "follow_topics_you_create": "Śledź tematy, które tworzysz",
     "grouptitle": "Wybierz tytuł grupy, który chcesz wyświetlać",
diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json
index 59ae2a5047..e681f5664f 100644
--- a/public/language/pt_BR/error.json
+++ b/public/language/pt_BR/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Senha Inválida",
     "invalid-username-or-password": "Por favor especifique ambos nome de usuário e senha",
     "invalid-search-term": "Termo de pesquisa inválido",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Valor de paginação inválido, precisa ser entre no mínimo %1 e no máximo %2",
     "username-taken": "Nome de usuário já existe",
     "email-taken": "Email já cadastrado",
     "email-not-confirmed": "O seu email ainda não foi confirmado, por favor clique aqui para confirmar seu email.",
@@ -27,6 +27,7 @@
     "password-too-long": "A senha é muito grande",
     "user-banned": "Usuário banido",
     "user-too-new": "Desculpe, é necessário que você aguarde %1 segundo(s) antes de fazer o seu primeiro post.",
+    "blacklisted-ip": "Desculpe, o seu endereço IP foi banido desta comunidade. Se você acha que isso é um engano, por favor contate um administrador.",
     "no-category": "A categoria não existe",
     "no-topic": "O tópico não existe",
     "no-post": "O post não existe",
@@ -50,7 +51,7 @@
     "still-uploading": "Aguarde a conclusão dos uploads.",
     "file-too-big": "O tamanho máximo permitido de arquivo é de %1 kB - por favor faça upload de um arquivo menor",
     "guest-upload-disabled": "O upload por visitantes foi desabilitado",
-    "already-favourited": "Você já adicionou este post aos favoritos",
+    "already-favourited": "Você já favoritou este post",
     "already-unfavourited": "Você já removeu este post dos favoritos",
     "cant-ban-other-admins": "Você não pode banir outros administradores!",
     "cant-remove-last-admin": "Você é o único administrador. Adicione outro usuário como administrador antes de remover a si mesmo como admin",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "A mensagem de chat é muito longa",
     "cant-edit-chat-message": "Você não tem permissão para editar esta mensagem",
     "cant-remove-last-user": "Você não pode excluir o último usuário",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Você não possui permissão para deletar esta mensagem",
     "reputation-system-disabled": "O sistema de reputação está desabilitado.",
     "downvoting-disabled": "Negativação está desabilitada",
     "not-enough-reputation-to-downvote": "Você não possui reputação suficiente para negativar este post",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Por favor use o seu nome de usuário para fazer login",
     "invite-maximum-met": "Você já convidou o número máximo de pessoas (%1 de %2).",
     "no-session-found": "Nenhuma sessão de login encontrada!",
-    "not-in-room": "User not in room"
+    "not-in-room": "O usuário não está na sala",
+    "no-users-in-room": "Nenhum usuário nesta sala",
+    "cant-kick-self": "Você não pode kickar a si mesmo do grupo"
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json
index fe803d80f6..cb79fa97d5 100644
--- a/public/language/pt_BR/global.json
+++ b/public/language/pt_BR/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "postado em %1 %2 por %3",
     "user_posted_ago": "%1 postou %2",
     "guest_posted_ago": "Visitante postou %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "editado por último por %1",
     "norecentposts": "Nenhum  Post Recente",
     "norecenttopics": "Sem Tópicos Recentes",
     "recentposts": "Posts Recentes",
@@ -86,5 +86,9 @@
     "delete_all": "Deletar Tudo",
     "map": "Mapa",
     "sessions": "Sessões de Login",
-    "ip_address": "Endereço IP"
+    "ip_address": "Endereço IP",
+    "enter_page_number": "Digite o número da página",
+    "upload_file": "Fazer upload de arquivo",
+    "upload": "Upload",
+    "allowed-file-types": "Os tipos de arquivo permitidos são %1"
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/groups.json b/public/language/pt_BR/groups.json
index 91c729622d..ed5727555f 100644
--- a/public/language/pt_BR/groups.json
+++ b/public/language/pt_BR/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Oculto",
     "details.hidden_help": "Se habilitado, este grupo não se encontrará na listagem de grupos e os usuários terão de ser convivados manualmente",
     "details.delete_group": "Deletar Grupo",
+    "details.private_system_help": "Grupos particulares estão desabilitados em escala de sistema, esta opção não é válida",
     "event.updated": "Os detalhes do grupo foram atualizados",
     "event.deleted": "O grupo \"%1\" foi deletado",
     "membership.accept-invitation": "Aceitar Convite",
@@ -48,5 +49,6 @@
     "membership.join-group": "Entrar no Grupo",
     "membership.leave-group": "Deixar Grupo",
     "membership.reject": "Rejeitar",
-    "new-group.group_name": "Nome do Grupo:"
+    "new-group.group_name": "Nome do Grupo:",
+    "upload-group-cover": "Fazer upload de capa do grupo"
 }
\ No newline at end of file
diff --git a/public/language/pt_BR/modules.json b/public/language/pt_BR/modules.json
index 1b186bb717..597e750246 100644
--- a/public/language/pt_BR/modules.json
+++ b/public/language/pt_BR/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 está digitando ...",
     "chat.user_has_messaged_you": "%1 te enviou uma mensagem.",
     "chat.see_all": "Ver todos os chats",
+    "chat.mark_all_read": "Marcar todas as conversas como lidas",
     "chat.no-messages": "Por favor, escolha um destinatário para visualizar o histórico de conversas",
     "chat.no-users-in-room": "Nenhum usuário nesta sala",
     "chat.recent-chats": "Conversas Recentes",
diff --git a/public/language/pt_BR/notifications.json b/public/language/pt_BR/notifications.json
index b1f5f21914..ae335eca3a 100644
--- a/public/language/pt_BR/notifications.json
+++ b/public/language/pt_BR/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> e %2 outros deram voto positivo ao seu post em <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> moveu seu post para <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> se mudou <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> adicionou seu tópico aos favoritos em <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> e <strong>%2</strong> adicionaram seu post aos favoritos em <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> e %2 outros adicionaram seu post aos favoritos em <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> favoritou o teu post em <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> e <strong>%2</strong> favoritaram o teu post em <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> e %2 outros favoritaram o teu post em <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> sinalizou um post em <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> e <strong>%2</strong> sinalizaram um post em <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> e %2 outros sinalizaram um post em <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> e <strong>%2</strong> começaram a lhe acompanhar.",
     "user_started_following_you_multiple": "<strong>%1</strong> e %2 outros começaram a lhe acompanhar.",
     "new_register": "<strong>%1</strong> lhe enviou um pedido de cadastro.",
+    "new_register_multiple": "Há <strong>%1</strong> pedidos de registro aguardando revisão.",
     "email-confirmed": "Email Confirmado",
     "email-confirmed-message": "Obrigado por validar o seu email. Agora sua conta está plenamente ativada.",
     "email-confirm-error-message": "Houve um problema ao validar o seu endereço de email. Talvez o código era invalido ou tenha expirado.",
diff --git a/public/language/pt_BR/pages.json b/public/language/pt_BR/pages.json
index cbbecbf80b..ac5238bb22 100644
--- a/public/language/pt_BR/pages.json
+++ b/public/language/pt_BR/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Tópicos populares deste mês",
     "popular-alltime": "Tópicos populares de todos os tempos",
     "recent": "Tópicos Recentes",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Posts Sinalizados",
     "users/online": "Usuários Online",
     "users/latest": "Últimos Usuários",
     "users/sort-posts": "Usuários com mais posts",
     "users/sort-reputation": "Usuários com maior reputação",
-    "users/banned": "Banned Users",
+    "users/banned": "Usuários Banidos",
     "users/search": "Pesquisa de Usuários",
     "notifications": "Notificações",
     "tags": "Tags",
@@ -33,12 +33,13 @@
     "account/posts": "Posts feitos por %1",
     "account/topics": "Tópicos criados por %1",
     "account/groups": "Grupos de %1",
-    "account/favourites": "Posts Favoritos de %1",
+    "account/favourites": "Posts Favoritados por %1",
     "account/settings": "Configurações de Usuário",
     "account/watched": "Tópicos assistidos por %1",
     "account/upvoted": "Posts votados positivamente por %1",
     "account/downvoted": "Posts votados negativamente por %1",
     "account/best": "Melhores posts de %1",
+    "confirm": "Email Confirmado",
     "maintenance.text": "%1 está atualmente sob manutenção. Por favor retorne em outro momento.",
     "maintenance.messageIntro": "Adicionalmente, o administrador deixou esta mensagem:",
     "throttled.text": "%1 está atualmente indisponível devido a excesso de contingente. Por favor retorne em outro momento."
diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json
index 0601402aa0..5f2d6257c1 100644
--- a/public/language/pt_BR/topic.json
+++ b/public/language/pt_BR/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Por favor se cadastre ou entre para assinar à este tópico.",
     "markAsUnreadForAll.success": "Tópico marcado como não lido para todos.",
     "mark_unread": "Marcar como não lidas",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Tópico marcado como não-lido.",
     "watch": "Acompanhar",
     "unwatch": "Desacompanhar",
     "watch.title": "Seja notificado sobre novas respostas neste tópico",
@@ -67,7 +67,7 @@
     "confirm_fork": "Ramificar",
     "favourite": "Favoritar",
     "favourites": "Favoritos",
-    "favourites.has_no_favourites": "Você não tem nenhum item nos favoritos, adicione algo aos favoritos para ver aqui!",
+    "favourites.has_no_favourites": "Você ainda não adicionou quaisquer posts aos favoritos.",
     "loading_more_posts": "Carregando Mais Posts",
     "move_topic": "Mover Tópico",
     "move_topics": "Mover Tópicos",
diff --git a/public/language/pt_BR/uploads.json b/public/language/pt_BR/uploads.json
new file mode 100644
index 0000000000..232e568e92
--- /dev/null
+++ b/public/language/pt_BR/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Fazendo upload do arquivo...",
+    "select-file-to-upload": "Escolha um arquivo para fazer upload!",
+    "upload-success": "Upload realizado com sucesso!",
+    "maximum-file-size": "No máximo %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json
index 5f01d4eeeb..21df201b80 100644
--- a/public/language/pt_BR/user.json
+++ b/public/language/pt_BR/user.json
@@ -39,6 +39,7 @@
     "change_username": "Mudar nome de usuário",
     "change_email": "Mudar email",
     "edit": "Editar",
+    "edit-profile": "Editar Perfil",
     "default_picture": "Ícone Padrão",
     "uploaded_picture": "Foto Carregada",
     "upload_new_picture": "Carregar Nova Foto",
@@ -55,10 +56,11 @@
     "password": "Senha",
     "username_taken_workaround": "O nome de usuário que você escolheu já existia, então nós o alteramos um pouquinho. Agora você é conhecido como <strong>%1</strong>",
     "password_same_as_username": "A sua senha é igual ao seu nome de usuário, por favor escolha outra senha.",
+    "password_same_as_email": "Tua senha é a mesma que o teu email, por favor escolha outra senha.",
     "upload_picture": "Carregar Foto",
     "upload_a_picture": "Carregue uma Foto",
     "remove_uploaded_picture": "Remover Foto Enviada",
-    "image_spec": "Você pode fazer upload apenas de arquivos PNG, JPG ou BMP",
+    "upload_cover_picture": "Fazer upload de imagem de capa ",
     "settings": "Configurações",
     "show_email": "Mostrar Meu Email",
     "show_fullname": "Mostrar Meu Nome Completo",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Abrir links externos em nova aba",
     "enable_topic_searching": "Habilitar Pesquisa dentro de Tópico",
     "topic_search_help": "Se habilitado, a pesquisa dentro do tópico irá substituir a pesquisa padrão do seu navegador. Assim, você poderá pesquisar pelo tópico inteiro, e não apenas pelo o que está sendo exibido na tela.",
+    "scroll_to_my_post": "Após postar uma réplica, mostre o novo post",
     "follow_topics_you_reply_to": "Seguir tópicos que você responde",
     "follow_topics_you_create": "Seguir tópicos que você cria",
     "grouptitle": "Escolha o título do grupo que você deseja exibir",
diff --git a/public/language/pt_BR/users.json b/public/language/pt_BR/users.json
index 635d5fdb34..0cd0b190d5 100644
--- a/public/language/pt_BR/users.json
+++ b/public/language/pt_BR/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Topicos Não-Lidos",
     "categories": "Categorias",
     "tags": "Tags",
-    "no-users-found": "No users found!"
+    "no-users-found": "Nenhum usuário encontrado!"
 }
\ No newline at end of file
diff --git a/public/language/ro/error.json b/public/language/ro/error.json
index 82407bac70..56750a42a0 100644
--- a/public/language/ro/error.json
+++ b/public/language/ro/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Parola prea lunga.",
     "user-banned": "Utilizator banat",
     "user-too-new": "Imi pare rau dar trebuie sa astepti %1 secunda(e) pentru a posta prima oara.",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Categoria nu exista.",
     "no-topic": "Topicul nu exista.",
     "no-post": "Post-ul nu exista.",
@@ -50,8 +51,8 @@
     "still-uploading": "Te rugăm să aștepți până se termină uploadul.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Nu poți bana alți administratori!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/ro/global.json b/public/language/ro/global.json
index 280a6dfb2b..501638ec86 100644
--- a/public/language/ro/global.json
+++ b/public/language/ro/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Şterge Tot",
     "map": "Hartă",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/ro/groups.json b/public/language/ro/groups.json
index 28471f1bff..60719c2bf9 100644
--- a/public/language/ro/groups.json
+++ b/public/language/ro/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json
index a63e9edd7c..f1f0bdf9d6 100644
--- a/public/language/ro/modules.json
+++ b/public/language/ro/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 scrie ...",
     "chat.user_has_messaged_you": "%1 ți-a trimis un mesaj.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Selectează un recipient pentru a vedea istoria mesajelor chat",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Conversații Recente",
diff --git a/public/language/ro/notifications.json b/public/language/ro/notifications.json
index d771741795..9684a7d9cc 100644
--- a/public/language/ro/notifications.json
+++ b/public/language/ro/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> a adăugat mesajul tău la favorite în <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> a semnalizat un mesaj în <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email confirmat",
     "email-confirmed-message": "Îți mulțumim pentru validarea emailului. Contul tău este acuma activat.",
     "email-confirm-error-message": "A fost o problemă cu activarea adresei tale de email. Poate codul de activare a fost invalid sau expirat.",
diff --git a/public/language/ro/pages.json b/public/language/ro/pages.json
index 13a8e26fb1..b90accee42 100644
--- a/public/language/ro/pages.json
+++ b/public/language/ro/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 este momentan în mentenanță. Întoarce-te în curând!",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json
index e6186c8225..5503fa8c3d 100644
--- a/public/language/ro/topic.json
+++ b/public/language/ro/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Categoriile dezactivate sunt decolorate cu gri",
     "confirm_move": "Mută",
     "confirm_fork": "Bifurcă",
-    "favourite": "Favorit",
-    "favourites": "Favorite",
-    "favourites.has_no_favourites": "Nu ai nici un mesaj favorit, adaugă mesaje la favorit pentru a le putea vedea aici!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Se încarcă mai multe mesaje",
     "move_topic": "Mută Subiect",
     "move_topics": "Mută Subiecte",
diff --git a/public/language/ro/uploads.json b/public/language/ro/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/ro/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/ro/user.json b/public/language/ro/user.json
index 2637691632..8a61af9abf 100644
--- a/public/language/ro/user.json
+++ b/public/language/ro/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Vizualizări",
     "reputation": "Reputație",
-    "favourites": "Favorite",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "Urmărit de",
     "following": "Îi urmărește pe",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Editează",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Poză uploadată",
     "upload_new_picture": "Uploadează poză nouă",
@@ -55,10 +56,11 @@
     "password": "Parolă",
     "username_taken_workaround": "Numele de utilizator pe care l-ai cerut este deja luat, așa că l-am modificat puțin. Acum ești cunoscut ca <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Uploadează poză",
     "upload_a_picture": "Uploadează o poză",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Setări",
     "show_email": "Arată adresa mea de email",
     "show_fullname": "Show My Full Name",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/ru/email.json b/public/language/ru/email.json
index f415bae1e8..a4b378b897 100644
--- a/public/language/ru/email.json
+++ b/public/language/ru/email.json
@@ -21,9 +21,9 @@
     "digest.cta": "Нажмите здесь для просмотра %1",
     "digest.unsub.info": "Вам была выслана сводка новостей в соответствии с Вашими настройками.",
     "digest.no_topics": "Нет активных тем за указанный период времени: %1",
-    "digest.day": "day",
-    "digest.week": "week",
-    "digest.month": "month",
+    "digest.day": "день",
+    "digest.week": "неделя",
+    "digest.month": "месяц",
     "notif.chat.subject": "Новое сообщение от %1",
     "notif.chat.cta": "Нажмите для продолжения диалога",
     "notif.chat.unsub.info": "Вы получили это уведомление в соответствии с настройками подписок.",
diff --git a/public/language/ru/error.json b/public/language/ru/error.json
index 907a8f9f83..efaab28fa9 100644
--- a/public/language/ru/error.json
+++ b/public/language/ru/error.json
@@ -24,9 +24,10 @@
     "confirm-email-already-sent": "Сообщение для подтверждения уже выслано на E-mail. Повторная отправка возможна через %1 мин.",
     "username-too-short": "Слишком короткое имя пользователя",
     "username-too-long": "Имя пользователя слишком длинное",
-    "password-too-long": "Password too long",
+    "password-too-long": "Пароль слишком длинный",
     "user-banned": "Пользователь заблокирован",
     "user-too-new": "Вы можете написать свое первой сообщение через %1 сек.",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Категория не существует",
     "no-topic": "Тема не существует",
     "no-post": "Сообщение не существует",
@@ -50,8 +51,8 @@
     "still-uploading": "Пожалуйста, подождите завершения загрузки.",
     "file-too-big": "Слишком большой файл. Максимальный размер: %1 Кбайт.",
     "guest-upload-disabled": "Загрузка для гостей была отключена",
-    "already-favourited": "Вы уже добавили это сообщение в избранное",
-    "already-unfavourited": "Вы уже удалили это сообщение из избранного",
+    "already-favourited": "Вы уже добавили это сообщение в закладки",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Вы не можете забанить других администраторов!",
     "cant-remove-last-admin": "Вы единственный администратор. Назначьте другого пользователя администратором, прежде чем складывать с себя полномочия админа",
     "invalid-image-type": "Неверный формат изображения. Поддерживаемые форматы: %1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Слишком длинное сообщение чата",
     "cant-edit-chat-message": "У вас нет доступа для редактирования этого сообщения",
     "cant-remove-last-user": "Вы не можете убрать последнего пользователя",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "У вас нет доступа на удаление этого сообщения",
     "reputation-system-disabled": "Система репутации отключена.",
     "downvoting-disabled": "Понижение оценки отключено",
     "not-enough-reputation-to-downvote": "У Вас недостаточно репутации для понижения оценки сообщения",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Пожалуйста, используйте своё имя пользователя для входа.",
     "invite-maximum-met": "Вы пригласили максимальное количество людей (%1 из %2).",
     "no-session-found": "Сессия входа не найдена!",
-    "not-in-room": "User not in room"
+    "not-in-room": "Пользователь не в комнате",
+    "no-users-in-room": "В этой комнате нет пользователей",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/ru/global.json b/public/language/ru/global.json
index f6f1703c69..3c6a09c0b5 100644
--- a/public/language/ru/global.json
+++ b/public/language/ru/global.json
@@ -49,7 +49,7 @@
     "users": "Пользователи",
     "topics": "Темы",
     "posts": "Сообщения",
-    "best": "Best",
+    "best": "Лучшие",
     "upvoted": "Upvoted",
     "downvoted": "Downvoted",
     "views": "Просмотры",
@@ -86,5 +86,9 @@
     "delete_all": "Удалить все",
     "map": "Карта",
     "sessions": "Сессии входа",
-    "ip_address": "IP адрес"
+    "ip_address": "IP адрес",
+    "enter_page_number": "Введите номер страницы",
+    "upload_file": "Загрузить файл",
+    "upload": "Загрузить",
+    "allowed-file-types": "Разрешенные форматы файлов %1"
 }
\ No newline at end of file
diff --git a/public/language/ru/groups.json b/public/language/ru/groups.json
index c9d711a028..eeeca0e8fb 100644
--- a/public/language/ru/groups.json
+++ b/public/language/ru/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Скрыто",
     "details.hidden_help": "Если включено, группа не будет показываться в списках, а пользователи должны приглашаться вручную",
     "details.delete_group": "Удалить группу",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Настройки группы обновлены",
     "event.deleted": "Группа \"%1\" удалена",
     "membership.accept-invitation": "Принять приглашение",
@@ -48,5 +49,6 @@
     "membership.join-group": "Вступить",
     "membership.leave-group": "Покинуть",
     "membership.reject": "Отклонить",
-    "new-group.group_name": "Название группы:"
+    "new-group.group_name": "Название группы:",
+    "upload-group-cover": "Загрузить обложку группы"
 }
\ No newline at end of file
diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json
index e6f53b3f79..07ecb488c8 100644
--- a/public/language/ru/modules.json
+++ b/public/language/ru/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 печатает ...",
     "chat.user_has_messaged_you": "%1 отправил вам сообщение.",
     "chat.see_all": "Посмотреть все чаты",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Пожалуйста, выберите собеседника для просмотра истории сообщений",
     "chat.no-users-in-room": "В этой комнате нет пользователей",
     "chat.recent-chats": "Последние переписки",
diff --git a/public/language/ru/notifications.json b/public/language/ru/notifications.json
index a6c76e8aaa..21c0a3b46c 100644
--- a/public/language/ru/notifications.json
+++ b/public/language/ru/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> и %2 другие проголосовали за ваше сообщение в <strong>%3</strong>",
     "moved_your_post": "<strong>%1</strong> переместил Ваше сообщение в <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> переместил <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> добавил в избранное Ваше сообщение в <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> и <strong>%2</strong> добавили в избранное Ваше сообщение в <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> и %2 другие добавили в избранное ваше сообщение в <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> пометил сообщение в <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> и <strong>%2</strong> пометили ваше сообщение в <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> и %2 другие пометили ваше сообщение <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> и <strong>%2</strong> подписались на вас.",
     "user_started_following_you_multiple": "<strong>%1</strong> и %2 подписались на вас.",
     "new_register": "<strong>%1</strong> отправил запрос на регистрацию.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email подтвержден",
     "email-confirmed-message": "Спасибо за подтверждение Вашего Email-адреса. Ваш аккаунт активирован.",
     "email-confirm-error-message": "Ошибка проверки Email-адреса. Возможно, код неверен, либо у него истек срок действия.",
diff --git a/public/language/ru/pages.json b/public/language/ru/pages.json
index 61718d5a69..962dba881c 100644
--- a/public/language/ru/pages.json
+++ b/public/language/ru/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Популярные темы этого месяца",
     "popular-alltime": "Популярные темы за все время",
     "recent": "Последние темы",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Отмеченные сообщения",
     "users/online": "В сети",
     "users/latest": "Новые пользователи",
     "users/sort-posts": "Пользователи по кол-ву сообщений",
     "users/sort-reputation": "Пользователи по кол-ву репутации",
-    "users/banned": "Banned Users",
+    "users/banned": "Заблокированные пользователи",
     "users/search": "Поиск пользователей",
     "notifications": "Уведомления",
     "tags": "Теги",
@@ -33,12 +33,13 @@
     "account/posts": "Сообщение от %1",
     "account/topics": "Тема создана %1",
     "account/groups": "%1 Групп",
-    "account/favourites": "%1 Избранных сообщений",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Настройки пользователя",
     "account/watched": "Тему смотрели %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
-    "account/best": "Best posts made by %1",
+    "account/best": "Лучшие сообщения написанные %1",
+    "confirm": "Email подтвержден",
     "maintenance.text": "%1 в настоящее время на обслуживании. Пожалуйста, возвращайтесь позже.",
     "maintenance.messageIntro": "Администратор оставил сообщение:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json
index 04a3c5894d..00ca832430 100644
--- a/public/language/ru/topic.json
+++ b/public/language/ru/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Пожалуйста зарегистрируйтесь, или войдите под своим аккаунтом, чтобы подписаться на эту тему.",
     "markAsUnreadForAll.success": "Тема помечена как непрочитанная для всех.",
     "mark_unread": "Отметить как непрочитанное",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Тема помечена как непрочитанная.",
     "watch": "Следить",
     "unwatch": "Не следить",
     "watch.title": "Сообщать мне об ответах в этой теме",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Отключенные категории затемнены",
     "confirm_move": "Перенести",
     "confirm_fork": "Ответвление",
-    "favourite": "Избранное",
-    "favourites": "Избранные",
-    "favourites.has_no_favourites": "У вас нет избранного, добавьте несколько сообщений в избранное, чтобы увидеть их здесь",
+    "favourite": "Закладка",
+    "favourites": "Закладки",
+    "favourites.has_no_favourites": "Вы еще не добавили ни одно сообщение в закладки.",
     "loading_more_posts": "Загружаем еще сообщения",
     "move_topic": "Перенести тему",
     "move_topics": "Перенести темы",
diff --git a/public/language/ru/uploads.json b/public/language/ru/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/ru/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/ru/user.json b/public/language/ru/user.json
index 36758fad50..5e14dc8004 100644
--- a/public/language/ru/user.json
+++ b/public/language/ru/user.json
@@ -22,7 +22,7 @@
     "profile": "Профиль",
     "profile_views": "Просмотров профиля",
     "reputation": "Репутация",
-    "favourites": "Избранное",
+    "favourites": "Закладки",
     "watched": "Просмотров",
     "followers": "Читателей",
     "following": "Читаемых",
@@ -35,14 +35,15 @@
     "unfollow": "Не читать",
     "more": "Ещё",
     "profile_update_success": "Профиль обновлен!",
-    "change_picture": "Изменить фотографию",
+    "change_picture": "Изменить аватар",
     "change_username": "Изменить имя пользователя",
     "change_email": "Изменить Email",
     "edit": "Редактировать",
+    "edit-profile": "Редактировать профиль",
     "default_picture": "Иконка по умолчанию",
-    "uploaded_picture": "Загруженные фотографии",
-    "upload_new_picture": "Загрузить новую фотографию",
-    "upload_new_picture_from_url": "Загрузить новое изображение с адреса URL",
+    "uploaded_picture": "Загруженный аватар",
+    "upload_new_picture": "Загрузить новый",
+    "upload_new_picture_from_url": "Указать по URL",
     "current_password": "Текущий пароль",
     "change_password": "Изменить пароль",
     "change_password_error": "Неверный пароль!",
@@ -55,10 +56,11 @@
     "password": "Пароль",
     "username_taken_workaround": "Логин, который Вы запросили, уже занят. Мы его немного изменили. Теперь Ваш  логин <strong>%1</strong>",
     "password_same_as_username": "Ваш пароль совпадает с именем пользователя, пожалуйста выберете другой пароль.",
-    "upload_picture": "Загрузить фотографию",
-    "upload_a_picture": "Загрузить фотографию",
-    "remove_uploaded_picture": "Удалить загруженные фотографии",
-    "image_spec": "Вы можете загружать только PNG, JPG или BMP файлы",
+    "password_same_as_email": "Ваш пароль такой же как email, пожалуйста, укажите другой пароль.",
+    "upload_picture": "Указать изображение",
+    "upload_a_picture": "Загрузить изображение",
+    "remove_uploaded_picture": "Удалить аватар",
+    "upload_cover_picture": "Загрузить обложку",
     "settings": "Настройки",
     "show_email": "Показывать мой Email",
     "show_fullname": "Показывать Полное Имя",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Открывать внешние ссылки в новых вкладках",
     "enable_topic_searching": "Активировать поиск внутри тем",
     "topic_search_help": "Если опция включена, поиск в теме будет осуществляться за счёт собственного поиска, который позволит искать во всей теме, а не только в загруженных сообщениях",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Следить за темами в которых вы отвечаете",
     "follow_topics_you_create": "Следить за темами которые вы создаёте",
     "grouptitle": "Выберите бейдж группы для отображения",
@@ -97,7 +100,7 @@
     "select-skin": "Выбрать скин",
     "select-homepage": "Укажите главную страницу",
     "homepage": "Главная страница",
-    "homepage_description": "Select a page to use as the forum homepage or 'None' to use the default homepage.",
+    "homepage_description": "Укажите страницу, которую хотите использовать как главную страницу форума или 'None', что бы использовать страницу по умолчанию.",
     "custom_route": "Custom Homepage Route",
     "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")",
     "sso.title": "Сервис единого входа",
diff --git a/public/language/ru/users.json b/public/language/ru/users.json
index 6ab7df5038..40c180685a 100644
--- a/public/language/ru/users.json
+++ b/public/language/ru/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Непрочитанные темы",
     "categories": "Категории",
     "tags": "Теги",
-    "no-users-found": "No users found!"
+    "no-users-found": "Пользователи не найдены!"
 }
\ No newline at end of file
diff --git a/public/language/rw/category.json b/public/language/rw/category.json
index e09c8b0787..5bfd9286eb 100644
--- a/public/language/rw/category.json
+++ b/public/language/rw/category.json
@@ -12,5 +12,5 @@
     "ignore": "Ihorere",
     "watch.message": "Uzajya ubu ukurikirana ibishya byongewe muri iki cyiciro",
     "ignore.message": "Ubu urekeye aho kuzajya ubona ibishya byongewe muri iki cyiciro",
-    "watched-categories": "Watched categories"
+    "watched-categories": "Ibyiciro Bikurikirwa"
 }
\ No newline at end of file
diff --git a/public/language/rw/email.json b/public/language/rw/email.json
index 138f1f5f14..626f11dec4 100644
--- a/public/language/rw/email.json
+++ b/public/language/rw/email.json
@@ -21,9 +21,9 @@
     "digest.cta": "Kanda hano kugirango usure %1",
     "digest.unsub.info": "Izi ngingo z'ingenzi zakohererejwe kuko waziyandikishijeho",
     "digest.no_topics": "Nta biganiro bishyushye byagaragaye mu gihe gishize cya %1",
-    "digest.day": "day",
-    "digest.week": "week",
-    "digest.month": "month",
+    "digest.day": "umunsi",
+    "digest.week": "icyumweru",
+    "digest.month": "ukwezi",
     "notif.chat.subject": "Ubutumwa bwo mu gikari bwaturutse kuri %1",
     "notif.chat.cta": "Kanda hano kugirango ukomeze",
     "notif.chat.unsub.info": "Iri tangazo rijyanye n'ubutumwa bwo mu gikari waryohererejwe kubera ko wabihisemo mu byo uzajya umenyeshwa",
diff --git a/public/language/rw/error.json b/public/language/rw/error.json
index 5584e6b5f3..a86168d3ef 100644
--- a/public/language/rw/error.json
+++ b/public/language/rw/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Umuntu wirukanwe",
     "user-too-new": "Wihangena kuko usabwa gutegereza amasegonda (isegonda) %1 mbere yo gushyiraho ikintu cyawe cya mbere",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Icyiciro kitabaho",
     "no-topic": "Ikiganiro kitabaho",
     "no-post": "Icyashyizweho kitabaho",
@@ -50,8 +51,8 @@
     "still-uploading": "Tegereza gupakira bibanze birangire.",
     "file-too-big": "Ubunini bwemewe bushoboka bw'ifayilo ni kB %1. Gerageza upakire ifayilo ntoyaho",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Wari wararangije gutonesha iki ngiki",
-    "already-unfavourited": "Wari wararekeye aho gutonesha iki ngiki",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Ntabwo wakwirukana abandi bayobozi!",
     "cant-remove-last-admin": "Ni wowe muyobozi wenyine. Ongeramo undi muntu nk'umuyobozi mbere y'uko wikura ku buyobozi",
     "invalid-image-type": "Ubwoko bw'ifoto wahisemo ntibwemewe. Hemewe gusa: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Koresha izina ry'umukoresha ryawe kugirango winjiremo",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "Nta muntu uri muri iki gikari",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/rw/global.json b/public/language/rw/global.json
index e543582ea0..aecc3f305e 100644
--- a/public/language/rw/global.json
+++ b/public/language/rw/global.json
@@ -33,7 +33,7 @@
     "header.notifications": "Amatangazo",
     "header.search": "Shaka",
     "header.profile": "Ishusho",
-    "header.navigation": "Navigation",
+    "header.navigation": "Ukureba",
     "notifications.loading": "Amatangazo Araje",
     "chats.loading": "Ubutumwa Buraje",
     "motd.welcome": "Urakaza neza kuri NodeBB, urubuga rujyanye n'ibihe bizaza",
@@ -49,9 +49,9 @@
     "users": "Abantu",
     "topics": "Ibiganiro",
     "posts": "Ibyashyizweho",
-    "best": "Best",
-    "upvoted": "Upvoted",
-    "downvoted": "Downvoted",
+    "best": "Byiza",
+    "upvoted": "Byakunzwe",
+    "downvoted": "Byagawe",
     "views": "Byarebwe",
     "reputation": "Amanota",
     "read_more": "komeza usome",
@@ -59,19 +59,19 @@
     "posted_ago_by_guest": "%1 bishyizweho na Umushyitsi",
     "posted_ago_by": "%1 bishyizweho na %2",
     "posted_ago": "%1 biriho",
-    "posted_in": "posted in %1",
-    "posted_in_by": "posted in %1 by %2",
+    "posted_in": "byashyizwe muri %1",
+    "posted_in_by": "byashyizwe muri %1 na %2",
     "posted_in_ago": "%2 bishyizwe muri %1",
     "posted_in_ago_by": "%2 bishyizwe muri %1 na %3",
     "user_posted_ago": "%2 %1 ashyizeho",
     "guest_posted_ago": "%1 Umushyitsi ashyizeho",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "biheruka guhindurwaho na %1",
     "norecentposts": "Nta Biherutseho",
     "norecenttopics": "Nta Biganiro Biherutse",
     "recentposts": "Ibiherutseho",
     "recentips": "Aderesi za IP Ziheruka Gusura",
     "away": "Ahandi",
-    "dnd": "Do not disturb",
+    "dnd": "Nta Kurogoya",
     "invisible": "Nta Kugaragara",
     "offline": "Nta Murongo",
     "email": "Email",
@@ -84,7 +84,11 @@
     "follow": "Kurikira",
     "unfollow": "Reka Gukurikira",
     "delete_all": "Siba Byose",
-    "map": "Map",
-    "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "map": "Ikarita",
+    "sessions": "Ukwinjiramo",
+    "ip_address": "Aderesi ya IP",
+    "enter_page_number": "Shyiramo nimero ya paji",
+    "upload_file": "Pakira ifayilo",
+    "upload": "Pakira",
+    "allowed-file-types": "Ubwoko bw'amafayilo bwemewe ni %1"
 }
\ No newline at end of file
diff --git a/public/language/rw/groups.json b/public/language/rw/groups.json
index 982f067a39..12ec0b7193 100644
--- a/public/language/rw/groups.json
+++ b/public/language/rw/groups.json
@@ -12,9 +12,9 @@
     "invited.none": "Nta banyamuryango batumiwe bahari",
     "invited.uninvite": "Kuraho Ubutumire",
     "invited.search": "Shaka umuntu wo gutumira muri iri tsinda",
-    "invited.notification_title": "You have been invited to join <strong>%1</strong>",
-    "request.notification_title": "Group Membership Request from <strong>%1</strong>",
-    "request.notification_text": "<strong>%1</strong> has requested to become a member of <strong>%2</strong>",
+    "invited.notification_title": "Utumiwe kwinjira muri <strong>%1</strong>",
+    "request.notification_title": "Ubusabe bwo Kujya mu Itsinda Buturutse <strong>%1</strong>",
+    "request.notification_text": "<strong>%1</strong> yasabye kuba umunyamuryango w'itsinda rya <strong>%2</strong>",
     "cover-save": "Bika",
     "cover-saving": "Kubika",
     "details.title": "Ibijyanye n'Itsinda",
@@ -24,7 +24,7 @@
     "details.has_no_posts": "Uyu munyamuryango ntabwo arashyiraho ikintu na kimwe",
     "details.latest_posts": "Ibiheruka Gushyirwaho",
     "details.private": "Yigenga",
-    "details.disableJoinRequests": "Disable join requests",
+    "details.disableJoinRequests": "Guhagarika ubusabe bwo kwinjira",
     "details.grant": "Tanga/Ambura Ubuyobozi",
     "details.kick": "Tera",
     "details.owner_options": "Ubuyobozi bw'Itsinda",
@@ -41,6 +41,7 @@
     "details.hidden": "Ahishe",
     "details.hidden_help": "Nubyemera, iri tsinda ntabwo rizajya rigaragara ku rutonde rw'andi matsinda kandi abantu bazajya basabwa kuritumirwamo buri wese ku giti cye mbere yo kurijyamo",
     "details.delete_group": "Senya Itsinda",
+    "details.private_system_help": "Amatsinda aheza ntabwo ari kwemerera aha, hano ntabwo byahahindurirwa",
     "event.updated": "Amakuru ku itsinda yahinduweho bijyanye n'igihe",
     "event.deleted": "Itsinda rya \"%1\" ryakuweho",
     "membership.accept-invitation": "Emera Ubutumire",
@@ -48,5 +49,6 @@
     "membership.join-group": "Injira mu Itsinda",
     "membership.leave-group": "Va mu Itsinda",
     "membership.reject": "Hakanira",
-    "new-group.group_name": "Izina ry'Itsinda:"
+    "new-group.group_name": "Izina ry'Itsinda:",
+    "upload-group-cover": "Shyiraho ifoto yo hejuru iranga itsinda"
 }
\ No newline at end of file
diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json
index 3f68db32b7..e0aff84e80 100644
--- a/public/language/rw/modules.json
+++ b/public/language/rw/modules.json
@@ -5,9 +5,10 @@
     "chat.no_active": "Nta biganiro byo mu gikari ufite. ",
     "chat.user_typing": "%1 ari kwandika ...",
     "chat.user_has_messaged_you": "%1 yagusigiye ubutumwa.",
-    "chat.see_all": "See all chats",
+    "chat.see_all": "Reba ubutumwa bwose",
+    "chat.mark_all_read": "Garagaza ubutumwa nk'ubwasomwe",
     "chat.no-messages": "Hitamo umuntu ushaka kurebera ibyo mwandikiranye",
-    "chat.no-users-in-room": "No users in this room",
+    "chat.no-users-in-room": "Nta muntu uri muri iki gikari",
     "chat.recent-chats": "Ubutumwa Buheruka",
     "chat.contacts": "Abo Kuvugisha",
     "chat.message-history": "Ubutumwa Bwahise",
@@ -16,9 +17,9 @@
     "chat.seven_days": "Iminsi 7",
     "chat.thirty_days": "Iminsi 30",
     "chat.three_months": "Amezi 3",
-    "chat.delete_message_confirm": "Are you sure you wish to delete this message?",
-    "chat.roomname": "Chat Room %1",
-    "chat.add-users-to-room": "Add users to room",
+    "chat.delete_message_confirm": "Wiringiye neza ko ushaka gusiba ubu butumwa?",
+    "chat.roomname": "Igikari cya %1",
+    "chat.add-users-to-room": "Ongera abantu mu gikari",
     "composer.compose": "Andika",
     "composer.show_preview": "Bona Uko Biza Gusa",
     "composer.hide_preview": "Hisha Uko Biza Gusa",
@@ -27,11 +28,11 @@
     "composer.discard": "Wiringiye neza ko ushaka kureka kubishyiraho?",
     "composer.submit_and_lock": "Shyiraho kandi Unafungirane",
     "composer.toggle_dropdown": "Hindura Icyerekezo",
-    "composer.uploading": "Uploading %1",
-    "bootbox.ok": "OK",
-    "bootbox.cancel": "Cancel",
-    "bootbox.confirm": "Confirm",
-    "cover.dragging_title": "Cover Photo Positioning",
-    "cover.dragging_message": "Drag the cover photo to the desired position and click \"Save\"",
-    "cover.saved": "Cover photo image and position saved"
+    "composer.uploading": "Ugupakira %1",
+    "bootbox.ok": "Sawa",
+    "bootbox.cancel": "Isubire",
+    "bootbox.confirm": "Emeza",
+    "cover.dragging_title": "Kuringaniza Ifoto yo Hejuru",
+    "cover.dragging_message": "Kurura ifoto yo hejuru mu cyerekezo ushaka ubundi ubike ibirangijwe",
+    "cover.saved": "Ibyatunganyijwe ku ifoto yo hejuru byafashe"
 }
\ No newline at end of file
diff --git a/public/language/rw/notifications.json b/public/language/rw/notifications.json
index 93abd5898f..60fa3be43d 100644
--- a/public/language/rw/notifications.json
+++ b/public/language/rw/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> yatonesheje <strong>%2</strong> washyizeho.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> yatambikanye ikintu muri <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> yasabye kwandikwa.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Yemejwe",
     "email-confirmed-message": "Urakoze kugaragaza ko email yawe ikora. Ubu ngubu konte yawe irakora nta kabuza. ",
     "email-confirm-error-message": "Havutse ikibazo mu gushaka kumenya niba email yawe ikora. Ushobora kuba wakoresheje kode itari yo cyangwa se yarengeje igihe. ",
diff --git a/public/language/rw/pages.json b/public/language/rw/pages.json
index 26c0ac71e9..b59cebf627 100644
--- a/public/language/rw/pages.json
+++ b/public/language/rw/pages.json
@@ -1,45 +1,46 @@
 {
     "home": "Imbere",
     "unread": "Ibiganiro Bitarasomwa",
-    "popular-day": "Popular topics today",
-    "popular-week": "Popular topics this week",
-    "popular-month": "Popular topics this month",
-    "popular-alltime": "All time popular topics",
+    "popular-day": "Ibiganiro bikunzwe uyu munsi",
+    "popular-week": "Ibiganiro bikunzwe iki cyumweru",
+    "popular-month": "Ibiganiro bikunzwe uku kwezi",
+    "popular-alltime": "Ibiganiro byakunzwe ibihe byose",
     "recent": "Ibiganiro Biheruka",
-    "flagged-posts": "Flagged Posts",
-    "users/online": "Online Users",
-    "users/latest": "Latest Users",
-    "users/sort-posts": "Users with the most posts",
-    "users/sort-reputation": "Users with the most reputation",
-    "users/banned": "Banned Users",
-    "users/search": "User Search",
+    "flagged-posts": "Ibyatambikanywe",
+    "users/online": "Abariho",
+    "users/latest": "Abashya",
+    "users/sort-posts": "Abantu bashyizeho byinshi",
+    "users/sort-reputation": "Abantu bafite amanota menshi",
+    "users/banned": "Abantu Bakumiriwe",
+    "users/search": "Gushaka Abantu",
     "notifications": "Amatangazo",
     "tags": "Ibimenyetso",
     "tag": "Ibiganiro bifite ibimenyetso bya \"%1\"",
-    "register": "Register an account",
-    "login": "Login to your account",
-    "reset": "Reset your account password",
-    "categories": "Categories",
-    "groups": "Groups",
-    "group": "%1 group",
-    "chats": "Chats",
-    "chat": "Chatting with %1",
-    "account/edit": "Editing \"%1\"",
-    "account/edit/password": "Editing password of \"%1\"",
-    "account/edit/username": "Editing username of \"%1\"",
-    "account/edit/email": "Editing email of \"%1\"",
-    "account/following": "People %1 follows",
-    "account/followers": "People who follow %1",
-    "account/posts": "Posts made by %1",
-    "account/topics": "Topics created by %1",
-    "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
-    "account/settings": "User Settings",
-    "account/watched": "Topics watched by %1",
-    "account/upvoted": "Posts upvoted by %1",
-    "account/downvoted": "Posts downvoted by %1",
-    "account/best": "Best posts made by %1",
+    "register": "Fungura Konte",
+    "login": "Injira muri konte yawe",
+    "reset": "Tangiza bundi bushya konte yawe",
+    "categories": "Ibyiciro",
+    "groups": "Amatsinda",
+    "group": "Itsinda %1 ",
+    "chats": "Mu Gikari",
+    "chat": "Ukuganira na %1",
+    "account/edit": "Uguhindura \"%1\"",
+    "account/edit/password": "Uguhindura ijambobanga rya \"%1\"",
+    "account/edit/username": "Uguhindura izina rya \"%1\"",
+    "account/edit/email": "Uguhindura email ya \"%1\"",
+    "account/following": "Abantu %1 akurikira",
+    "account/followers": "Abantu bakurikira %1",
+    "account/posts": "Ibyashyizweho na %1",
+    "account/topics": "Ibiganiro byatangijwe na %1",
+    "account/groups": "Amatsinda ya %1",
+    "account/favourites": "Ibyazigamwe na %1",
+    "account/settings": "Itunganya",
+    "account/watched": "Ibiganiro bikurikirwa na %1",
+    "account/upvoted": "Ibiganiro byakunzwe na %1",
+    "account/downvoted": "Ibiganiro byanzwe na %1",
+    "account/best": "Ibihebuje byashyizweho na %1",
+    "confirm": "Email Yemejwe",
     "maintenance.text": "%1 ntiboneka kuko ubu iri gutunganywa. Muze kongera kugaruka. ",
     "maintenance.messageIntro": "Byongeye, kandi, umuyobozi yasize ubu butumwa: ",
-    "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
+    "throttled.text": "% ntibonetse kubera ukunanirwa. Uze kugaruka ikindi gihe. "
 }
\ No newline at end of file
diff --git a/public/language/rw/topic.json b/public/language/rw/topic.json
index 2f3e53b21d..b3ba723ec7 100644
--- a/public/language/rw/topic.json
+++ b/public/language/rw/topic.json
@@ -13,7 +13,7 @@
     "notify_me": "Uzajye umenyeshwa ibisubizo bishya kuri iki kiganiro",
     "quote": "Terura",
     "reply": "Subiza",
-    "reply-as-topic": "Reply as topic",
+    "reply-as-topic": "Bishyireho nk'ikiganiro",
     "guest-login-reply": "Injiramo maze usubize",
     "edit": "Hinduraho",
     "delete": "Siba",
@@ -26,7 +26,7 @@
     "tools": "Ibikoresho",
     "flag": "Tambikana",
     "locked": "Birafungiranye",
-    "bookmark_instructions": "Click here to return to the last unread post in this thread.",
+    "bookmark_instructions": "Kanda hano kugirango usubire ahari ibitarasomwe biheruka muri iki kiganiro.",
     "flag_title": "Bimenyeshe ubuyobozi",
     "flag_success": "Bimaze kumenyeshwa ubuyobozi ngo bikurikiranwe. ",
     "deleted_message": "Iki kiganiro cyamaze gukurwaho. Abantu babifitiye uburenganzira ni bo bonyine bashobora kukibona. ",
@@ -34,8 +34,8 @@
     "not_following_topic.message": "Ntabwo uzongera kujya umenyeshwa ku bibera muri iki kiganiro. ",
     "login_to_subscribe": "Ba umunyamuryango cyangwa winjiremo niba ushaka kwiyandikisha kuri iki kiganiro. ",
     "markAsUnreadForAll.success": "Ikiganiro kigizwe nk'icyasomwe na bose",
-    "mark_unread": "Mark unread",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread": "Garagaza nk'ibyasomwe",
+    "mark_unread.success": "Ikiganiro cyagaragajwe nk'icyasomwe.",
     "watch": "Cunga",
     "unwatch": "Rekeraho Gucunga",
     "watch.title": "Ujye umenyeshwa ibyongerwaho bishya kuri iki kiganiro",
@@ -51,7 +51,7 @@
     "thread_tools.move_all": "Byimure Byose",
     "thread_tools.fork": "Gabanyaho ku Kiganiro",
     "thread_tools.delete": "Kuraho Ikiganiro",
-    "thread_tools.delete-posts": "Delete Posts",
+    "thread_tools.delete-posts": "Siba Icyashizweho",
     "thread_tools.delete_confirm": "Wiringiye neza ko ushaka gukuraho iki kiganiro?",
     "thread_tools.restore": "Subizaho Ikiganiro",
     "thread_tools.restore_confirm": "Wiringiye neza ko ushaka kugarura iki kiganiro?",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Ibyiciro bitagaragazwa birasa n'ibipfutse",
     "confirm_move": "Imura",
     "confirm_fork": "Gabanyaho",
-    "favourite": "Tonesha",
-    "favourites": "Ibyatoneshejwe",
-    "favourites.has_no_favourites": "Nta kintu na kimwe wari watonesha. Tonesha ibintu bimwe na bimwe kugirango ujye ubibona aha!",
+    "favourite": "Zigama",
+    "favourites": "Ibyazigamwe",
+    "favourites.has_no_favourites": "Ntabwo urazigama ikintu na kimwe.",
     "loading_more_posts": "Ibindi Biraje",
     "move_topic": "Imura Ikiganiro",
     "move_topics": "Imura Ibiganiro",
@@ -78,7 +78,7 @@
     "fork_topic_instruction": "Kanda ku byashizweho ushaka kugabanyaho",
     "fork_no_pids": "Nta kintu wahisemo!",
     "fork_success": "Umaze kugabanyaho ku kiganiro! Kanda hano ugezwe ku kiganiro cyavutse. ",
-    "delete_posts_instruction": "Click the posts you want to delete/purge",
+    "delete_posts_instruction": "Kanda ku bintu ushaka guhisha/gusiba",
     "composer.title_placeholder": "Shyira umutwe w'ikiganiro cyawe aha...",
     "composer.handle_placeholder": "Izina",
     "composer.discard": "Byihorere",
@@ -101,12 +101,12 @@
     "newest_to_oldest": "Ibya Vuba Ujya ku bya Kera",
     "most_votes": "Amajwi yiganje",
     "most_posts": "Ibyashyizweho byiganje",
-    "stale.title": "Create new topic instead?",
-    "stale.warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?",
-    "stale.create": "Create a new topic",
-    "stale.reply_anyway": "Reply to this topic anyway",
-    "link_back": "Re: [%1](%2)",
+    "stale.title": "Urashaka gutangiza ahubwo ikiganiro gishya?",
+    "stale.warning": "Ikiganiro ushaka kuvugaho cyarashaje. Wahitamo gutangiza ikiganiro gishya ariko wenda ukagaragaza kino mu gisubizo uza gushyiraho?",
+    "stale.create": "Tangiza ikiganiro gishya",
+    "stale.reply_anyway": "Vuga kuri iki kiganiro nubundi",
+    "link_back": "Igisubizo: [%1](%2)",
     "spam": "Spam",
-    "offensive": "Offensive",
-    "custom-flag-reason": "Enter a flagging reason"
+    "offensive": "Ugukomeretsanya",
+    "custom-flag-reason": "Shyiramo impamvu yo gutambikana"
 }
\ No newline at end of file
diff --git a/public/language/rw/uploads.json b/public/language/rw/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/rw/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/rw/user.json b/public/language/rw/user.json
index a489bf8a32..9ca89d0160 100644
--- a/public/language/rw/user.json
+++ b/public/language/rw/user.json
@@ -12,7 +12,7 @@
     "delete_account": "Siba Konte",
     "delete_account_confirm": "Wiringiye neza ko ushaka gusiba konte yawe? <br /><strong>Numara kuyisiba ntabwo urabasha kwisubira kandi nturabasha kugarura ibyo wari ufiteho</strong><br /><br />Shyiramo izina ryawe kugirango wemeze ko koko ushaka gusenya iyi konte.",
     "delete_this_account_confirm": "Wiringiye neza ko ushaka gusiba iyi konte? <br /><strong>Ntabwo uri bubashe kwisubira kandi ntabwo urabasha gusubirana ibyo wari ufiteho numara kuyisiba</strong><br /><br />",
-    "account-deleted": "Account deleted",
+    "account-deleted": "Konte yasibwe",
     "fullname": "Izina Ryuzuye",
     "website": "Urubuga",
     "location": "Ahantu",
@@ -22,7 +22,7 @@
     "profile": "Ishusho",
     "profile_views": "Ishusho Yarebwe",
     "reputation": "Amanota",
-    "favourites": "Ibitoneshwa",
+    "favourites": "Ibyazigamwe",
     "watched": "Ibikurikiranwa",
     "followers": "Abamukurikira",
     "following": "Akurikira",
@@ -30,16 +30,17 @@
     "signature": "Intero",
     "birthday": "Itariki y'Amavuko",
     "chat": "Mu Gikari",
-    "chat_with": "Chat with %1",
+    "chat_with": "Ganira na %1",
     "follow": "Kurikira",
     "unfollow": "Ntukurikire",
     "more": "Ibindi",
     "profile_update_success": "Ishusho yashyizwe ku gihe nta ngorane!",
     "change_picture": "Hindura Ifoto",
-    "change_username": "Change Username",
-    "change_email": "Change Email",
+    "change_username": "Hindura Izina",
+    "change_email": "Hindura Email",
     "edit": "Hinduraho",
-    "default_picture": "Default Icon",
+    "edit-profile": "Hinduraho ku Ishusho",
+    "default_picture": "Akamenyetso Gasanzwe",
     "uploaded_picture": "Ifoto Yapakiwe",
     "upload_new_picture": "Pakira Ifoto Nshya",
     "upload_new_picture_from_url": "Pakira Ifoto Nshya Ukoresheje URL",
@@ -54,11 +55,12 @@
     "confirm_password": "Emeza Ijambobanga",
     "password": "Ijambobanga",
     "username_taken_workaround": "Izina ushaka kujya ukoresha twasanze ryarafashwe. Ntugire impungenge kuko twakuboneye iryo byenda kumera kimwe. Uzaba uzwi ku izina rya <strong>%1</strong>",
-    "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_username": "Ijambobanga ryawe rirasa neza n'izina ukoresha; hitamo irindi jambobanga.",
+    "password_same_as_email": "Ijambobanga ryawe rirasa neza na email yawe; hitamo irindi jambobanga.",
     "upload_picture": "Gushyiraho ifoto",
     "upload_a_picture": "Shyiraho ifoto",
-    "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "remove_uploaded_picture": "Kuraho Ifoto",
+    "upload_cover_picture": "Pakira ifoto yo hejuru",
     "settings": "Itunganya",
     "show_email": "Hagaragazwe Email Yanjye",
     "show_fullname": "Hagaragazwe Izina Ryuzuye Ryanjye",
@@ -77,9 +79,9 @@
     "has_no_posts": "Uyu muntu nta kintu arashyiraho. ",
     "has_no_topics": "Uyu muntu nta kiganiro aratangiza na kimwe. ",
     "has_no_watched_topics": "Uyu muntu ntabwo arakurikira ikiganiro na kimwe.",
-    "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.",
-    "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.",
-    "has_no_voted_posts": "This user has no voted posts",
+    "has_no_upvoted_posts": "Uyu muntu ntabwo arashima icyashyizweho na kimwe.",
+    "has_no_downvoted_posts": "Uyu muntu ntabwo aragaya icyashizweho na kimwe. ",
+    "has_no_voted_posts": "Uyu muntu ntabwo aragira ikintu yashimiwe gushyiraho",
     "email_hidden": "Email Yahishwe",
     "hidden": "byahishwe",
     "paginate_description": "Gabanya ibiganiro n'ibyashyizweho mu ma paji aho kugirango umuntu ajye amanuka ubudahagarara ",
@@ -90,17 +92,18 @@
     "open_links_in_new_tab": "Fungurira imirongo ijya hanze mu idirishya rishya",
     "enable_topic_searching": "Emerera Ugushakira mu Kiganiro",
     "topic_search_help": "Nibyemerwa, ugushakira mu kiganiro bizajya biba ari byo bikorwa maze bitume umuntu abasha gushakira mu kiganiro hose aho gushakira kuri paji igaragarira amaso, imbere yawe gusa",
+    "scroll_to_my_post": "Nyuma yo gushyiraho igisubizo, hagaragare icyashyizweho gishya",
     "follow_topics_you_reply_to": "Kurikira ibiganiro ushyiraho ibisubizo",
     "follow_topics_you_create": "Kurikira ibiganiro uba watangije",
     "grouptitle": "Hitamo umutwe w'itsinda ushaka ko uzajya ugaragara",
     "no-group-title": "Nta mutwe w'itsinda",
-    "select-skin": "Select a Skin",
-    "select-homepage": "Select a Homepage",
-    "homepage": "Homepage",
-    "homepage_description": "Select a page to use as the forum homepage or 'None' to use the default homepage.",
-    "custom_route": "Custom Homepage Route",
-    "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")",
-    "sso.title": "Single Sign-on Services",
-    "sso.associated": "Associated with",
+    "select-skin": "Hitamo Uruhu",
+    "select-homepage": "Hitamo Paji y'Imbere",
+    "homepage": "Paji y'Imbere",
+    "homepage_description": "Hitamo paji yo kugaragaza imbere cyangwa ntuyihitemo kugirango hakoreshwe paji uru rubuga rwagennye",
+    "custom_route": "Umurongo Wundi wa Paji y'Imbere",
+    "custom_route_help": "Shyiramo izina ry'inzira, utiriwe ushyiraho akarongo (ni ukuvuga ni nko kwandika gusa \"ibiheruka\" cyangwa \"ibikunzwe\")",
+    "sso.title": "Kwinjiramo ukoreshe serivisi za SSO",
+    "sso.associated": "Bisanishijwe na",
     "sso.not-associated": "Click here to associate with"
 }
\ No newline at end of file
diff --git a/public/language/rw/users.json b/public/language/rw/users.json
index 67afa61e34..16993f0592 100644
--- a/public/language/rw/users.json
+++ b/public/language/rw/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Ibiganiro Bitarasomwa",
     "categories": "Ibyiciro",
     "tags": "Ibimenyetso",
-    "no-users-found": "No users found!"
+    "no-users-found": "Nta muntu wabonetse"
 }
\ No newline at end of file
diff --git a/public/language/sc/error.json b/public/language/sc/error.json
index ca4533474d..0709e823b6 100644
--- a/public/language/sc/error.json
+++ b/public/language/sc/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "User banned",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Category does not exist",
     "no-topic": "Topic does not exist",
     "no-post": "Post does not exist",
@@ -50,8 +51,8 @@
     "still-uploading": "Please wait for uploads to complete.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "You can't ban other admins!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/sc/global.json b/public/language/sc/global.json
index 09c4882bd4..586d9340bf 100644
--- a/public/language/sc/global.json
+++ b/public/language/sc/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Delete All",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/sc/groups.json b/public/language/sc/groups.json
index 2ac271a4c5..3c4f6ce638 100644
--- a/public/language/sc/groups.json
+++ b/public/language/sc/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json
index 58a5f145fb..89b779d53e 100644
--- a/public/language/sc/modules.json
+++ b/public/language/sc/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 is typing ...",
     "chat.user_has_messaged_you": "%1 has messaged you.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Please select a recipient to view chat message history",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Recent Chats",
diff --git a/public/language/sc/notifications.json b/public/language/sc/notifications.json
index e2efe63c40..5f7fd03816 100644
--- a/public/language/sc/notifications.json
+++ b/public/language/sc/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email Confirmed",
     "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
     "email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
diff --git a/public/language/sc/pages.json b/public/language/sc/pages.json
index 8af7d8e199..a2e82aa90f 100644
--- a/public/language/sc/pages.json
+++ b/public/language/sc/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json
index 5dd4e78fa4..1f58d8582d 100644
--- a/public/language/sc/topic.json
+++ b/public/language/sc/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Is Crezes Disativadas sunt postas in colore de chìghine",
     "confirm_move": "Move",
     "confirm_fork": "Partzi",
-    "favourite": "Preferidu",
-    "favourites": "Preferidos",
-    "favourites.has_no_favourites": "Non tenes perunu preferidu, pone intre is preferidos carchi arresonu pro ddu bìdere inoghe!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Càrriga Prus Arresonos",
     "move_topic": "Move Arresonada",
     "move_topics": "Move Topics",
diff --git a/public/language/sc/uploads.json b/public/language/sc/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/sc/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/sc/user.json b/public/language/sc/user.json
index d6d36d0c4a..2b30121863 100644
--- a/public/language/sc/user.json
+++ b/public/language/sc/user.json
@@ -22,7 +22,7 @@
     "profile": "Perfilu",
     "profile_views": "Bìsitas a su perfilu",
     "reputation": "Nodidos",
-    "favourites": "Preferidos",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "Sighidores",
     "following": "Sighende",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Acontza",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Immàgine Carrigada",
     "upload_new_picture": "Càrriga Immàgine Noa",
@@ -55,10 +56,11 @@
     "password": "Password",
     "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Càrriga immàgine",
     "upload_a_picture": "Càrriga un'immàgine",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Sèberos",
     "show_email": "Ammustra s'Email Mia",
     "show_fullname": "Show My Full Name",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/sk/error.json b/public/language/sk/error.json
index 3ad3058253..4d99825f9e 100644
--- a/public/language/sk/error.json
+++ b/public/language/sk/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Užívateľ je zakázaný",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Category does not exist",
     "no-topic": "Topic does not exist",
     "no-post": "Post does not exist",
@@ -50,8 +51,8 @@
     "still-uploading": "Prosím čakajte na dokončenie nahrávania",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Nemožte zakázať druhých adminov.",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/sk/global.json b/public/language/sk/global.json
index b3dd3aa13a..652bb0e62a 100644
--- a/public/language/sk/global.json
+++ b/public/language/sk/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Delete All",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/sk/groups.json b/public/language/sk/groups.json
index eaa2eea404..ee2368b97e 100644
--- a/public/language/sk/groups.json
+++ b/public/language/sk/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Hidden",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Group details have been updated",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json
index 61c82e062d..b9be2fb6c3 100644
--- a/public/language/sk/modules.json
+++ b/public/language/sk/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 práve píše ...",
     "chat.user_has_messaged_you": "%1 Vám zaslal správu.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Please select a recipient to view chat message history",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Recent Chats",
diff --git a/public/language/sk/notifications.json b/public/language/sk/notifications.json
index c151af7abd..160a4d9e86 100644
--- a/public/language/sk/notifications.json
+++ b/public/language/sk/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email bol potvrdený",
     "email-confirmed-message": "Ďakujeme za potvrdenie tvojho emailu. Účet je plne aktivovaný.",
     "email-confirm-error-message": "Vyskytla sa chyba pri overení tvojej emailovej adresy. ",
diff --git a/public/language/sk/pages.json b/public/language/sk/pages.json
index 53d8874789..f5d5c49231 100644
--- a/public/language/sk/pages.json
+++ b/public/language/sk/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json
index 8096d584e7..39f4ca03d8 100644
--- a/public/language/sk/topic.json
+++ b/public/language/sk/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Vypnuté (disabled) kategorie sú šedé.",
     "confirm_move": "Presunúť",
     "confirm_fork": "Rozdeliť",
-    "favourite": "Oblúbené",
-    "favourites": "Obľúbené",
-    "favourites.has_no_favourites": "Nemáte žiadne obľúbené príspevky, pridajte niektorý príspevok k obľúbeným a uvidíte ho tu!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Načítavanie viac príspevkov",
     "move_topic": "Presunúť tému",
     "move_topics": "Viac tém",
diff --git a/public/language/sk/uploads.json b/public/language/sk/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/sk/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/sk/user.json b/public/language/sk/user.json
index 2e721e8d5a..6baaf337ce 100644
--- a/public/language/sk/user.json
+++ b/public/language/sk/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Zobrazenie profilu",
     "reputation": "Reputácia",
-    "favourites": "Obľúbené",
+    "favourites": "Bookmarks",
     "watched": "Watched",
     "followers": "Nasledujú ho",
     "following": "Nasleduje",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Upraviť",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Nahraný obrázok",
     "upload_new_picture": "Nahrať nový obrázok",
@@ -55,10 +56,11 @@
     "password": "Heslo",
     "username_taken_workaround": "Vaše požadované prihlasovacie meno je už obsadené, tak sme si ho dovolili mierne upraviť. Budeme Vás evidovať ako <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Nahrať obrázok",
     "upload_a_picture": "Nahrať obrázok",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Nastavenia",
     "show_email": "Zobrazovať môj email v profile",
     "show_fullname": "Show My Full Name",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/sl/error.json b/public/language/sl/error.json
index a450802630..a0453bc488 100644
--- a/public/language/sl/error.json
+++ b/public/language/sl/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Uporabnik je blokiran",
     "user-too-new": "Oprostite, počakajte %1 sekund pred vašo prvo objavo",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategorija ne obstaja",
     "no-topic": "Tema ne obstaja",
     "no-post": "Objava ne obstaja",
@@ -50,8 +51,8 @@
     "still-uploading": "Prosimo počakajte, da se prenosi končajo.",
     "file-too-big": "Največja dovoljena velikost datoteke je %1 kB - prosimo naložite manjšo datoteko",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "To objavo ste že dodali med priljubljene",
-    "already-unfavourited": "To objavo ste že odstranili iz priljubljenih",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Ne morete blokirati drugih administratorjev!",
     "cant-remove-last-admin": "Ste edini administrator. Dodajte novega administratorja preden boste odstranili sebe.",
     "invalid-image-type": "Nedovoljen format slike. Dovoljeni formati so: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Uporabite svoje uporabniško ime za prijavo",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/sl/global.json b/public/language/sl/global.json
index 3f5101bdd4..c8ba8fe3d2 100644
--- a/public/language/sl/global.json
+++ b/public/language/sl/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Izbriši vse",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/sl/groups.json b/public/language/sl/groups.json
index 5779451fb7..bb5af04f68 100644
--- a/public/language/sl/groups.json
+++ b/public/language/sl/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Skrito",
     "details.hidden_help": "Skupina je verjetno skrita pred uporabniki, zato se lahko vanjo pridružijo zgolj tisti s povabilom",
     "details.delete_group": "Izbriši skupino",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Podatki o skupini so bili posodobljeni",
     "event.deleted": "Skupina %1 je bila izbrisana",
     "membership.accept-invitation": "Sprejmi povabilo",
@@ -48,5 +49,6 @@
     "membership.join-group": "Pridruži se skupini",
     "membership.leave-group": "Zapusti skupino",
     "membership.reject": "Zavrni",
-    "new-group.group_name": "Ime skupine:"
+    "new-group.group_name": "Ime skupine:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json
index c393634fd8..911a9f1588 100644
--- a/public/language/sl/modules.json
+++ b/public/language/sl/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 piše sporočilo...",
     "chat.user_has_messaged_you": "%1 ti je napisal/a sporočilo.",
     "chat.see_all": "Poglej vse klepete",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Za pogled zgodovine klepeta izberi prejemnika",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Zadnji klepeti",
diff --git a/public/language/sl/notifications.json b/public/language/sl/notifications.json
index 469f331406..37711ea13d 100644
--- a/public/language/sl/notifications.json
+++ b/public/language/sl/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> je dodal med priljubljene vašo objavo v <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong>je prijavil vašo objavo v <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> je poslal prošnjo za registracijo.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "E-mail naslov potrjen",
     "email-confirmed-message": "Hvala ker ste potrdili svoj naslov. Račun je sedaj aktiviran.",
     "email-confirm-error-message": "Prišlo je do napake pri preverjanju vašega e-mail naslova. Morda je bila koda napačna ali pa je potekla.",
diff --git a/public/language/sl/pages.json b/public/language/sl/pages.json
index 134a6e6f3e..3c206aadf4 100644
--- a/public/language/sl/pages.json
+++ b/public/language/sl/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Objave uporabnika %1",
     "account/topics": "Ustvarjene teme uporabnika %1",
     "account/groups": "Teme uporabnika %1",
-    "account/favourites": "Priljubljene objave uporabnika %1",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Uporabniške nastavitve",
     "account/watched": "Teme, ki jih spremlja uporabnik %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 je trenutno v prenovi. Prosimo pridite nazaj kasneje.",
     "maintenance.messageIntro": "Administrator obvešča:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/sl/topic.json b/public/language/sl/topic.json
index 1d842fe0fb..503f7ee250 100644
--- a/public/language/sl/topic.json
+++ b/public/language/sl/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Onemogočene kategorije so obarvane sivo",
     "confirm_move": "Premakni",
     "confirm_fork": "Razcepi",
-    "favourite": "Dodaj med priljubljene",
-    "favourites": "Priljubljene",
-    "favourites.has_no_favourites": "Nimaš priljubljenih objav, dodaj nekaj objav pod priljubljene.",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Nalagam več objav",
     "move_topic": "Premakni temo",
     "move_topics": "Premakni teme",
diff --git a/public/language/sl/uploads.json b/public/language/sl/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/sl/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/sl/user.json b/public/language/sl/user.json
index 916f4c5ed6..2f13808e59 100644
--- a/public/language/sl/user.json
+++ b/public/language/sl/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Ogledi",
     "reputation": "Naziv",
-    "favourites": "Priljubljene",
+    "favourites": "Bookmarks",
     "watched": "Zgodovina ogledov",
     "followers": "Sledilci",
     "following": "Sledim",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Uredi",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Naloži fotografijo",
     "upload_new_picture": "Naloži novo fotografijo",
@@ -55,10 +56,11 @@
     "password": "Geslo",
     "username_taken_workaround": "Predlagano uporabniško ime je že zasedeno, zato predlagamo <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Naloži fotografijo",
     "upload_a_picture": "Naloži fotografijo",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Nastavitve.",
     "show_email": "Pokaži moj e-poštni naslov.",
     "show_fullname": "Pokaži moj ime in priimek.",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Zunanje povezave odpri v novem zavihku",
     "enable_topic_searching": "Omogoči iskanje znotraj teme",
     "topic_search_help": "Če omogočite, bo iskanje prepisalo brskalnikove prevzete nastavitve in vam omogočilo iskanje skozi celotno temo.",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Spremljaj teme v katerih si sodeloval",
     "follow_topics_you_create": "Spremljaj teme, ki si jih ustvaril/a",
     "grouptitle": "Izberi ime skupine za prikaz",
diff --git a/public/language/sr/error.json b/public/language/sr/error.json
index e9e7ce0a01..25e4d1b130 100644
--- a/public/language/sr/error.json
+++ b/public/language/sr/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "User banned",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Категорија не постоји",
     "no-topic": "Тема не постоји",
     "no-post": "Порука не постоји",
@@ -50,8 +51,8 @@
     "still-uploading": "Please wait for uploads to complete.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "You can't ban other admins!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/sr/global.json b/public/language/sr/global.json
index aadbb16820..1891007d72 100644
--- a/public/language/sr/global.json
+++ b/public/language/sr/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Обриши све",
     "map": "Мапа",
     "sessions": "Логин сесија",
-    "ip_address": "IP адреса"
+    "ip_address": "IP адреса",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/sr/groups.json b/public/language/sr/groups.json
index 3e44a09035..91bd05e916 100644
--- a/public/language/sr/groups.json
+++ b/public/language/sr/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Скривена",
     "details.hidden_help": "Уколико је укључено, група неће бити видљива на списку група, и корисницима се позивнице морају слати ручно.",
     "details.delete_group": "Избришите групу",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Детаљи групе су ажурирани",
     "event.deleted": "Група „%1“ је обрисана",
     "membership.accept-invitation": "Прихватите позив",
@@ -48,5 +49,6 @@
     "membership.join-group": "Учланите се у групу",
     "membership.leave-group": "Напусти групу",
     "membership.reject": "Одбаци",
-    "new-group.group_name": "Име групе:"
+    "new-group.group_name": "Име групе:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json
index 73693f5f82..412f9fd207 100644
--- a/public/language/sr/modules.json
+++ b/public/language/sr/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 куца ...",
     "chat.user_has_messaged_you": "%1 вам посла поруку.",
     "chat.see_all": "Погледајте сва ћаскања",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Изаберите примаоца да бисте видели историју ћаскања",
     "chat.no-users-in-room": "Нема корисника у овој соби",
     "chat.recent-chats": "Недавна ћаскања",
diff --git a/public/language/sr/notifications.json b/public/language/sr/notifications.json
index 712538ba0e..52a2b2d949 100644
--- a/public/language/sr/notifications.json
+++ b/public/language/sr/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> и %2 осталих су гласали за вашу поруку у <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> је померио вашу поруку у <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> је померио <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> доду вашу поруку из <strong>%2</strong> у омиљене.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> и <strong>%2</strong> кажу да им се допада ваша порука у <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> и осталим %2 се допала ваша порука <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> означи поруку у <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> и <strong>%2</strong> су означили поруку у <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> и осталих %2 су означили поруку у <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> и <strong>%2</strong> су почели да вас прате.",
     "user_started_following_you_multiple": "<strong>%1</strong> и %2 других су почели да вас прате.",
     "new_register": "<strong>%1</strong> вам је послао захтев за регистрацију.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Е-пошта је је отврђена.",
     "email-confirmed-message": "Хвала на овери ваше е-поште. Ваш налог је сада у потпуности активан.",
     "email-confirm-error-message": "Дошло је до проблема са овером ваше е-поште. Можда је код неисправан или истекао.",
diff --git a/public/language/sr/pages.json b/public/language/sr/pages.json
index e3eae6f34b..0e7387583e 100644
--- a/public/language/sr/pages.json
+++ b/public/language/sr/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.",
     "maintenance.messageIntro": "Additionally, the administrator has left this message:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json
index cf7b76fc3e..37e042c18f 100644
--- a/public/language/sr/topic.json
+++ b/public/language/sr/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Disabled Categories are greyed out",
     "confirm_move": "Move",
     "confirm_fork": "Fork",
-    "favourite": "Favourite",
-    "favourites": "Favourites",
-    "favourites.has_no_favourites": "You don't have any favourites, favourite some posts to see them here!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Loading More Posts",
     "move_topic": "Move Topic",
     "move_topics": "Move Topics",
diff --git a/public/language/sr/uploads.json b/public/language/sr/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/sr/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/sr/user.json b/public/language/sr/user.json
index 2fe581051f..66280af2d3 100644
--- a/public/language/sr/user.json
+++ b/public/language/sr/user.json
@@ -22,7 +22,7 @@
     "profile": "Профил",
     "profile_views": "Прикази профила",
     "reputation": "Репутација",
-    "favourites": "Омиљени",
+    "favourites": "Bookmarks",
     "watched": "Надгледани",
     "followers": "Пратиоци",
     "following": "Прати",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Уређивање",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Послата слика",
     "upload_new_picture": "Слање нове слике",
@@ -55,10 +56,11 @@
     "password": "Лозинка",
     "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as <strong>%1</strong>",
     "password_same_as_username": "Ваша лозинка је иста као ваше име, промените лозинку",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Додајте слику",
     "upload_a_picture": "Upload a picture",
     "remove_uploaded_picture": "Уклоните додату слику",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Подешавања",
     "show_email": "Прикажи моју лозинку",
     "show_fullname": "Прикажи моје пуно име",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/sv/error.json b/public/language/sv/error.json
index 8f3513edaf..efb83e5014 100644
--- a/public/language/sv/error.json
+++ b/public/language/sv/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "Användare bannlyst",
     "user-too-new": "När du är ny medlem måste du vänta %1 sekund(er) innan du gör ditt första inlägg",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Kategorin finns inte",
     "no-topic": "Ämnet finns inte",
     "no-post": "Inlägget finns inte",
@@ -50,8 +51,8 @@
     "still-uploading": "Vänta medan uppladdningen slutförs.",
     "file-too-big": "Den maximalt tillåtna filstorleken är %1 kB - ladda upp en mindre fil",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Du har redan favoriserat det här inlägget",
-    "already-unfavourited": "Du har redan avfavoriserat det här inlägget",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Du kan inte bannlysa andra administratörer.",
     "cant-remove-last-admin": "Du är den enda administratören. Lägg till en annan användare som administratör innan du tar bort dig själv.",
     "invalid-image-type": "Ogiltig bildtyp. Tillåtna typer är: % 1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Använd ditt användarnamn för att logga in",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/sv/global.json b/public/language/sv/global.json
index d984b99cc5..a9cb801869 100644
--- a/public/language/sv/global.json
+++ b/public/language/sv/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Ta bort Alla",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/sv/groups.json b/public/language/sv/groups.json
index 7a9f14b04a..2fa0d40823 100644
--- a/public/language/sv/groups.json
+++ b/public/language/sv/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Dold",
     "details.hidden_help": "Om aktiverat kommer gruppen inte synas i grupplistan och användare måste bli inbjudna manuellt",
     "details.delete_group": "Ta bort grupp",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Gruppdetaljerna har uppdaterats",
     "event.deleted": "Gruppen \"%1\" har tagits bort",
     "membership.accept-invitation": "Acceptera inbjudan",
@@ -48,5 +49,6 @@
     "membership.join-group": "Gå med i grupp",
     "membership.leave-group": "Lämna grupp",
     "membership.reject": "Neka",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json
index f2a3722a31..ae9799ad2e 100644
--- a/public/language/sv/modules.json
+++ b/public/language/sv/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 skriver ...",
     "chat.user_has_messaged_you": "%1 har skickat ett medelande till dig.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Välj mottagare för att visa historik för chatmeddelande",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Senaste chattarna",
diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json
index b0879d499b..743451971a 100644
--- a/public/language/sv/notifications.json
+++ b/public/language/sv/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> har favoriserat ditt inlägg i <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flaggade ett inlägg i <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> skickade en registreringsförfrågan.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Epost bekräftad",
     "email-confirmed-message": "Tack för att du bekräftat din epostadress. Ditt konto är nu fullt ut aktiverat.",
     "email-confirm-error-message": "Det uppstod ett fel med att bekräfta din epostadress. Kanske var koden ogiltig eller har gått ut.",
diff --git a/public/language/sv/pages.json b/public/language/sv/pages.json
index 1e8f24f5cb..6953bf554e 100644
--- a/public/language/sv/pages.json
+++ b/public/language/sv/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "Avnändarinställningar",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 genomgår underhåll just nu. Vänligen kom tillbaka lite senare.",
     "maintenance.messageIntro": "Ytterligare så lämnade administratören detta meddelande:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json
index 119caa86e5..4fa03da4ee 100644
--- a/public/language/sv/topic.json
+++ b/public/language/sv/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Inaktiverade kategorier är utgråade",
     "confirm_move": "Flytta",
     "confirm_fork": "Grena",
-    "favourite": "Favorit",
-    "favourites": "Favoriter",
-    "favourites.has_no_favourites": "Du har inga favoriter, markera inlägg som favorit för att se dem här!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Laddar fler inlägg",
     "move_topic": "Flytta ämne",
     "move_topics": "Flytta ämnen",
diff --git a/public/language/sv/uploads.json b/public/language/sv/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/sv/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/sv/user.json b/public/language/sv/user.json
index a807bdb73f..ab893029e1 100644
--- a/public/language/sv/user.json
+++ b/public/language/sv/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Profil-visningar",
     "reputation": "Rykte",
-    "favourites": "Favoriter",
+    "favourites": "Bookmarks",
     "watched": "Bevakad",
     "followers": "Följare",
     "following": "Följer",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "Ändra",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "Uppladdad bild",
     "upload_new_picture": "Ladda upp ny bild",
@@ -55,10 +56,11 @@
     "password": "Lösenord",
     "username_taken_workaround": "Användarnamnet är redan upptaget, så vi förändrade det lite. Du kallas nu för <strong>%1</strong>",
     "password_same_as_username": "Ditt lösenord är samma som ditt användarnamn, välj ett annat lösenord.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Ladda upp bild",
     "upload_a_picture": "Ladda upp en bild",
     "remove_uploaded_picture": "Ta bort uppladdad bild",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Inställningar",
     "show_email": "Visa min epost",
     "show_fullname": "Visa Fullständigt Namn",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Öppna utgående länkar på ny flik",
     "enable_topic_searching": "Aktivera Sökning Inom Ämne",
     "topic_search_help": "Om aktiverat kommer sökning inom ämne överskrida webbläsarens vanliga funktionen för sökning bland sidor och tillåta dig att söka genom hela ämnet istället för det som endast visas på skärmen.",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Följ ämnen som du svarat på",
     "follow_topics_you_create": "Följ ämnen du skapat",
     "grouptitle": "Välj tittel för gruppen så som du vill att den ska visas",
diff --git a/public/language/th/error.json b/public/language/th/error.json
index 7d046a412b..5fa5730b94 100644
--- a/public/language/th/error.json
+++ b/public/language/th/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "User banned",
     "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "ยังไม่มี Category นี้",
     "no-topic": "ยังไม่มี Topic นี้",
     "no-post": "ยังไม่มี Post นี้",
@@ -50,8 +51,8 @@
     "still-uploading": "Please wait for uploads to complete.",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "You have already favourited this post",
-    "already-unfavourited": "You have already unfavourited this post",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "You can't ban other admins!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "Invalid image type. Allowed types are: %1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Please use your username to login",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/th/global.json b/public/language/th/global.json
index f655109acb..36acafc94d 100644
--- a/public/language/th/global.json
+++ b/public/language/th/global.json
@@ -86,5 +86,9 @@
     "delete_all": "Delete All",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/th/groups.json b/public/language/th/groups.json
index 5f40f59e30..ad202d2ceb 100644
--- a/public/language/th/groups.json
+++ b/public/language/th/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "ซ่อน",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "ข้อมูล Group ได้รับการบันทึกแล้ว",
     "event.deleted": "The group \"%1\" has been deleted",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/th/modules.json b/public/language/th/modules.json
index a81b6e5c04..15a7bb1fa5 100644
--- a/public/language/th/modules.json
+++ b/public/language/th/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 is typing ...",
     "chat.user_has_messaged_you": "%1 has messaged you.",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Please select a recipient to view chat message history",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "Recent Chats",
diff --git a/public/language/th/notifications.json b/public/language/th/notifications.json
index d6228f8518..dd81dfdb7e 100644
--- a/public/language/th/notifications.json
+++ b/public/language/th/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> has favourited your post in <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> flagged a post in <strong>%2</strong>",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Email ได้รับการยืนยันแล้ว",
     "email-confirmed-message": "ขอบคุณที่ยืนยัน Email ของคุณ บัญชีของคุณสามารถใช้งานได้แล้ว",
     "email-confirm-error-message": "มีปัญหาในการยืนยัน Email ของคุณ บางทีรหัสไม่ถูกต้องหรือหมดอายุแล้ว",
diff --git a/public/language/th/pages.json b/public/language/th/pages.json
index 5534337e1e..e017b8df5e 100644
--- a/public/language/th/pages.json
+++ b/public/language/th/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 กำลังอยู่ระหว่างการปิดปรับปรุงชั่วคราว กรุณาลองใหม่อีกครั้งในภายหลัง",
     "maintenance.messageIntro": "ผู้ดูแลระบบได้ฝากข้อความต่อไปนี้เอาไว้",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/th/topic.json b/public/language/th/topic.json
index 6e979ef48c..19cecadf9d 100644
--- a/public/language/th/topic.json
+++ b/public/language/th/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "หมวดหมู่ที่ปิดใช้งานจะเป็นสีเทา",
     "confirm_move": "ย้าย",
     "confirm_fork": "แยก",
-    "favourite": "ฮิต",
-    "favourites": "ฮิต",
-    "favourites.has_no_favourites": "คุณไม่ได้มีโพสต์ที่ชอบ รายการโพสต์ที่ชอบที่จะเห็นได้ที่นี่",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "โหลดโพสเพิ่มเติม",
     "move_topic": "ย้ายกระทู้",
     "move_topics": "Move Topics",
diff --git a/public/language/th/uploads.json b/public/language/th/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/th/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/th/user.json b/public/language/th/user.json
index 129c3f3c7c..092f19cf70 100644
--- a/public/language/th/user.json
+++ b/public/language/th/user.json
@@ -22,7 +22,7 @@
     "profile": "รายละเอียด",
     "profile_views": "ดูข้อมูลส่วนตัว",
     "reputation": "ชื่อเสียง",
-    "favourites": "ชอบ",
+    "favourites": "Bookmarks",
     "watched": "ดูแล้ว",
     "followers": "คนติดตาม",
     "following": "ติดตาม",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "แก้ไข",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "อัปโหลดรูป",
     "upload_new_picture": "อัพโหลดรูปใหม่",
@@ -55,10 +56,11 @@
     "password": "รหัสผ่าน",
     "username_taken_workaround": "ชื่อนี้มีคนใช้แล้ว เราเลยแก้ไขชื่อคุณ โดยคุณจะถูกรู้จักในชื่อ <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "อัปโหลดรูป",
     "upload_a_picture": "อัปโหลดรูป",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "ตั้งค่า",
     "show_email": "แสดงอีเมล์",
     "show_fullname": "แสดงชื่อจริง",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "เปิดใช้การค้นหาแบบ In-Topic",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/language/tr/error.json b/public/language/tr/error.json
index 3e9a52baef..bb03c66177 100644
--- a/public/language/tr/error.json
+++ b/public/language/tr/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "Geçersiz Şifre",
     "invalid-username-or-password": "Lütfen kullanıcı ismi ve parola girin.",
     "invalid-search-term": "Geçersiz arama",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Geçersiz sayfalama değeri, en az %1 ve en fazla %2 olabilir",
     "username-taken": "Kullanıcı İsmi Alınmış",
     "email-taken": "E-posta Alınmış",
     "email-not-confirmed": "E-postanız onaylanmamış, onaylamak için lütfen buraya tıklayın.",
@@ -27,6 +27,7 @@
     "password-too-long": "Parola çok uzun",
     "user-banned": "Kullanıcı Yasaklı",
     "user-too-new": "Özür dileriz, ilk iletinizi yapmadan önce %1 saniye beklemeniz gerekiyor",
+    "blacklisted-ip": "Üzgünüz, IP adresiniz, bu toplulukta yasaklandı. Bunun bir hata olduğunu düşünüyorsanız, bir yönetici ile irtibata geçiniz.",
     "no-category": "Kategori Yok",
     "no-topic": "Başlık Yok",
     "no-post": "İleti Yok",
@@ -50,8 +51,8 @@
     "still-uploading": "Lütfen yüklemelerin bitmesini bekleyin.",
     "file-too-big": "İzin verilen en büyük dosya boyutu %1 kb - lütfen daha küçük bir dosya yükleyin",
     "guest-upload-disabled": "Ziyaretçilerin yükleme yapması devre dışı bırakıldı",
-    "already-favourited": "Bu iletiyi zaten favorilerinize eklediniz",
-    "already-unfavourited": "Bu iletiyi zaten favorilerinizden çıkardınız",
+    "already-favourited": "Bu iletiyi zaten yer imlerinize eklediniz",
+    "already-unfavourited": "Bu iletiyi zaten yer imlerinizden çıkardınız",
     "cant-ban-other-admins": "Başka yöneticileri yasaklayamazsınız!",
     "cant-remove-last-admin": "Tek yönetici sizsiniz. Kendinizi adminlikten çıkarmadan önce başka bir kullanıcıyı admin olarak ekleyiniz",
     "invalid-image-type": "Geçersiz resim uzantısı. Izin verilen uzantılar: %1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "Sohbet mesajı çok uzun",
     "cant-edit-chat-message": "Bu mesajı düzenlemek için izin verilmez",
     "cant-remove-last-user": "Son kullanıcıyı silemezsiniz",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "Bu mesajı silmek için izin verilmez",
     "reputation-system-disabled": "Saygınlık sistemi kapatılmış.",
     "downvoting-disabled": "Aşagı oylama kapatılmış",
     "not-enough-reputation-to-downvote": "Bu iletiyi aşagı oylamak için yeterince saygınlığınız yok.",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "Lütfen giriş için kullanıcı adınızı kullanın",
     "invite-maximum-met": "Sen maksimum miktarda insanı davet ettin (%2 üzerinden %1).",
     "no-session-found": "Giriş yapılmış bir oturum bulunamadı!",
-    "not-in-room": "User not in room"
+    "not-in-room": "Odada kullanıcı yok",
+    "no-users-in-room": "Bu odada kullanıcı yok",
+    "cant-kick-self": "Kendinizi gruptan atamazsınız."
 }
\ No newline at end of file
diff --git a/public/language/tr/global.json b/public/language/tr/global.json
index 7c99284277..2cf32930e5 100644
--- a/public/language/tr/global.json
+++ b/public/language/tr/global.json
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "%1 içinde %3 tarafından %2 yayımlandı",
     "user_posted_ago": "%1 %2 yayımladı",
     "guest_posted_ago": "Ziyaretçi %1 yayımladı",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "en son düzenleyen %1",
     "norecentposts": "Güncel İletiler Yok",
     "norecenttopics": "Güncel Başlıklar Yok",
     "recentposts": "Güncel İletiler",
@@ -86,5 +86,9 @@
     "delete_all": "Hepsini Sil",
     "map": "Harita",
     "sessions": "Giriş Oturumları",
-    "ip_address": "IP Adresleri"
+    "ip_address": "IP Adresleri",
+    "enter_page_number": "Sayfa numarasını girin",
+    "upload_file": "Dosya yükle",
+    "upload": "Yükle",
+    "allowed-file-types": "İzin verilen dosya tipleri %1"
 }
\ No newline at end of file
diff --git a/public/language/tr/groups.json b/public/language/tr/groups.json
index afb293df5c..a67cd1b2cb 100644
--- a/public/language/tr/groups.json
+++ b/public/language/tr/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "Gizli",
     "details.hidden_help": "Bu grup eğer etkinse grup listelerinde bulunmaz, ve kullanıcılar bizzat davet eder",
     "details.delete_group": "Grubu Sil",
+    "details.private_system_help": "Özel gruplar sistem seviyesinde devre dışı bırakıldı. Bu seçenek hiçbir şeyi değiştirmeyecek.",
     "event.updated": "Grup detayları güncellenmiştir",
     "event.deleted": "\"%1\" grubu silinmiş",
     "membership.accept-invitation": "Daveti Kabul Et",
@@ -48,5 +49,6 @@
     "membership.join-group": "Gruba Katıl",
     "membership.leave-group": "Gruptan Ayrıl",
     "membership.reject": "Reddet",
-    "new-group.group_name": "Grup İsmi:"
+    "new-group.group_name": "Grup İsmi:",
+    "upload-group-cover": "Grup kapağı yükle"
 }
\ No newline at end of file
diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json
index c0edbdc1d8..b896b442a0 100644
--- a/public/language/tr/modules.json
+++ b/public/language/tr/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 yazıyor ...",
     "chat.user_has_messaged_you": "%1 size bir mesaj gönderdi.",
     "chat.see_all": "Bütün sohbetleri gör",
+    "chat.mark_all_read": "Bütün sohbetleri okundu işaretle",
     "chat.no-messages": "Lütfen sohbet geçmişini görmek için bir kontak seçin",
     "chat.no-users-in-room": "Bu odada hiç kullanıcı yok",
     "chat.recent-chats": "Güncel Sohbetler",
diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json
index e378b748e7..c4e7a2276b 100644
--- a/public/language/tr/notifications.json
+++ b/public/language/tr/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> ve %2 iki kişi daha <strong>%3</strong> içindeki gönderini beğendi.",
     "moved_your_post": "<strong>%1</strong> senin iletin <strong>%2</strong> taşındı",
     "moved_your_topic": "<strong>%1</strong> taşındı <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> iletinizi favorilerine ekledi. <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> ve <strong>%2</strong> <strong>%3</strong> içindeki gönderini favoriye ekledi.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> ve %2 iki kişi daha <strong>%3</strong> içindeki gönderini favoriye ekledi.",
+    "favourited_your_post_in": "<strong>%1</strong> <strong>%2</strong> içindeki gönderini yer imlerine ekledi.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> ve <strong>%2</strong> <strong>%3</strong> gönderini yer imlerine ekledi.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> ve %2 kişi daha <strong>%3</strong> gönderini yer imlerine ekledi.",
     "user_flagged_post_in": "<strong>%1</strong> bir iletiyi bayrakladı. <strong>%2</strong>",
     "user_flagged_post_in_dual": " <strong>%1</strong> ve <strong>%2</strong> <strong>%3</strong> gönderini bayrakladı",
     "user_flagged_post_in_multiple": "<strong>%1</strong> ve %2 kişi daha <strong>%3</strong> gönderini bayrakladı",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> ve <strong>%2</strong> seni takip etmeye başladı.\n",
     "user_started_following_you_multiple": "<strong>%1</strong> ve %2 kişi daha seni takip etmeye başladı.",
     "new_register": "<strong>%1</strong> kayıt olma isteği gönderdi.",
+    "new_register_multiple": "Beklemede <strong>%1</strong> kayıt olma isteği bulunmaktadır.",
     "email-confirmed": "E-posta onaylandı",
     "email-confirmed-message": "E-postanızı onaylandığınız için teşekkürler. Hesabınız tamamen aktive edildi.",
     "email-confirm-error-message": "E-posta adresinizi onaylarken bir hata oluştu. Kodunuz geçersiz ya da eski olabilir.",
diff --git a/public/language/tr/pages.json b/public/language/tr/pages.json
index 5f6856b98f..b076bf11d9 100644
--- a/public/language/tr/pages.json
+++ b/public/language/tr/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "Bu ayki popüler başlıklar",
     "popular-alltime": "En popüler başlıklar",
     "recent": "Güncel Konular",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "Bayraklanmış İletiler",
     "users/online": "Çevrimiçi Kullanıcılar",
     "users/latest": "En Yeni Kullanıcılar",
     "users/sort-posts": "En çok ileti gönderen kullanıcılar",
     "users/sort-reputation": "En çok saygınlığı olan kullanıcılar",
-    "users/banned": "Banned Users",
+    "users/banned": "Yasaklanmış Kullanıcılar",
     "users/search": "Kullanıcı Ara",
     "notifications": "Bildirimler",
     "tags": "Etiketler",
@@ -33,12 +33,13 @@
     "account/posts": "%1 tarafından gönderilen iletiler",
     "account/topics": "%1 tarafından gönderilen başlıklar",
     "account/groups": "%1 Kişisine Ait Gruplar",
-    "account/favourites": "%1'in Favori İletileri",
+    "account/favourites": "%1'in Yer imleri",
     "account/settings": "Kullanıcı Ayarları",
     "account/watched": "%1 tarafından izlenen konular",
     "account/upvoted": "%1 tarafından artılanan gönderiler",
     "account/downvoted": "%1 tarafından eksilenen gönderiler",
     "account/best": "%1 tarafından en iyi gönderiler",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 şu anda bakımda. Lütfen bir süre sonra tekrar deneyin.",
     "maintenance.messageIntro": "Ayrıca, yönetici şu mesaji bıraktı:",
     "throttled.text": "%1 şu anda kullanılamıyor. Lütfen daha sonra tekrar dene."
diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json
index b6616f0624..e12c92f005 100644
--- a/public/language/tr/topic.json
+++ b/public/language/tr/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "Lütfen bu iletiyi başlığa üye olmak için giriş yapın.",
     "markAsUnreadForAll.success": "Başlık herkes için okunmadı olarak işaretlendi.",
     "mark_unread": "Okunmadı olarak işaretle",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "Başlık okunmamış olarak işaretlendi.",
     "watch": "İzle",
     "unwatch": "İzleme",
     "watch.title": "Bu başlığa gelen yeni iletilerden haberdar ol",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "Etkin Olmayan Kategoriler soluklaştırılır",
     "confirm_move": "Taşı",
     "confirm_fork": "Ayır",
-    "favourite": "Favori",
-    "favourites": "Favoriler",
-    "favourites.has_no_favourites": "Favorilerde hiç iletiniz yok, görebilmek için iletileri favorilere ekleyin.",
+    "favourite": "Yer imi",
+    "favourites": "Yer imleri",
+    "favourites.has_no_favourites": "Yer imlerine eklenmiş hiçbir ileti yok.",
     "loading_more_posts": "Daha fazla ileti ",
     "move_topic": "Başlığı Taş",
     "move_topics": "Konuları Taşı",
diff --git a/public/language/tr/uploads.json b/public/language/tr/uploads.json
new file mode 100644
index 0000000000..255eb420d7
--- /dev/null
+++ b/public/language/tr/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Dosya yükleniyor...",
+    "select-file-to-upload": "Bir dosya seç!",
+    "upload-success": "Dosya yüklenmesi tamamlandı!",
+    "maximum-file-size": "Maksimum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/tr/user.json b/public/language/tr/user.json
index b7b5a65d10..1664a6ec81 100644
--- a/public/language/tr/user.json
+++ b/public/language/tr/user.json
@@ -22,7 +22,7 @@
     "profile": "Profil",
     "profile_views": "Profil Görüntülemeleri",
     "reputation": "Saygınlık",
-    "favourites": "Favoriler",
+    "favourites": "Yer imleri",
     "watched": "İzlendi",
     "followers": "Takipçiler",
     "following": "Takip Ediyor",
@@ -39,6 +39,7 @@
     "change_username": "Kullanıcı Adı Değiştir",
     "change_email": "Email Değiştir",
     "edit": "Düzenle",
+    "edit-profile": "Profil Düzenle",
     "default_picture": "Varsayılan İkon",
     "uploaded_picture": "Yüklenmiş Fotoğraflar",
     "upload_new_picture": "Yeni bir resim Yükle",
@@ -55,10 +56,11 @@
     "password": "Şifre",
     "username_taken_workaround": "İstediğiniz kullanıcı ismi zaten alınmış, bu yüzden biraz degiştirdik. Şimdiki kullanıcı isminiz <strong>%1</strong>",
     "password_same_as_username": "Parolanız kullanıcı adınız ile aynı, lütfen başka bir parola seçiniz.",
+    "password_same_as_email": "Şifreniz mail adresiniz ile aynı lütfen başka bir şifre seçin.",
     "upload_picture": "Resim Yükle",
     "upload_a_picture": "Bir Resim Yükle",
     "remove_uploaded_picture": "Yüklenmiş fotoğrafı kaldır",
-    "image_spec": "Sadece PNG, JPG veya BMP dosyalarını yükleyebilirsin.",
+    "upload_cover_picture": "Kapak fotoğrafı yükle",
     "settings": "Ayarlar",
     "show_email": "E-postamı göster",
     "show_fullname": "Tam ismimi göster",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Dışarı giden bağlantıları yeni sekmede aç",
     "enable_topic_searching": "Konu içi aramayı aktive et",
     "topic_search_help": "Aktive edilirse, konu içi arama tarayıcının normal arama davranışını değiştirerek tüm konuyu aramanızı sağlar",
+    "scroll_to_my_post": "Cevap yazdıktan sonra yeni gönderiyi göster",
     "follow_topics_you_reply_to": "Cevap verdiğim konuları takip et",
     "follow_topics_you_create": "Kendi konularımı takip et",
     "grouptitle": "Göstermek istediğiniz gurup başlığını seçin",
diff --git a/public/language/tr/users.json b/public/language/tr/users.json
index b4b5afe4e4..fa0bc51096 100644
--- a/public/language/tr/users.json
+++ b/public/language/tr/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "Okunmamış Başlıklar",
     "categories": "Kategoriler",
     "tags": "Etiketler",
-    "no-users-found": "No users found!"
+    "no-users-found": "Kullanıcı bulunamadı!"
 }
\ No newline at end of file
diff --git a/public/language/vi/category.json b/public/language/vi/category.json
index 1f06930274..77836d6492 100644
--- a/public/language/vi/category.json
+++ b/public/language/vi/category.json
@@ -1,16 +1,16 @@
 {
-    "category": "Danh mục",
-    "subcategories": "Danh mục con",
+    "category": "Chuyên mục",
+    "subcategories": "Chuyên mục con",
     "new_topic_button": "Chủ đề mới",
     "guest-login-post": "Đăng nhập để viết bài",
     "no_topics": "<strong>Không có bài viết trong danh mục này.</strong><br />Hãy đăng một bài viết mới.",
     "browsing": "đang xem",
     "no_replies": "Chưa có bình luận nào",
     "no_new_posts": "Không có bài mới.",
-    "share_this_category": "Chia sẻ thư mục này",
+    "share_this_category": "Chia sẻ chuyên mục này",
     "watch": "Theo dõi",
     "ignore": "Bỏ qua",
     "watch.message": "Bạn đang theo dõi các cập nhật của danh mục này",
-    "ignore.message": "Bạn đang gỡ bỏ theo dõi danh mục này",
-    "watched-categories": "Watched categories"
+    "ignore.message": "Bạn đã tắt cập nhật từ chuyên mục này",
+    "watched-categories": "Chuyên mục đang theo dõi"
 }
\ No newline at end of file
diff --git a/public/language/vi/email.json b/public/language/vi/email.json
index cf8f8e86e1..de77b60125 100644
--- a/public/language/vi/email.json
+++ b/public/language/vi/email.json
@@ -1,11 +1,11 @@
 {
-    "password-reset-requested": "Yêu cầu tạo lại mật khẩu - %1!",
-    "welcome-to": "Chào mừng đến với %1",
+    "password-reset-requested": "Yêu cầu phục hồi mật khẩu - %1!",
+    "welcome-to": "Chào mừng bạn đến với %1",
     "invite": "Lời mời từ %1",
     "greeting_no_name": "Xin chào",
     "greeting_with_name": "Xin chào %1",
     "welcome.text1": "Cảm ơn bạn đã đăng ký tại %1!",
-    "welcome.text2": "Để kích hoạt đầy đủ tính năng của tài khoản, chúng tôi cần xác định bạn là chủ của địa chỉ email mà bạn đã đăng ký.",
+    "welcome.text2": "Để kích hoạt đầy đủ tính năng của tài khoản, chúng tôi cần xác nhận bạn là chủ của địa chỉ email mà bạn đã đăng ký.",
     "welcome.text3": "Quản trị viên đã chấp nhận đơn đăng ký của bạn. Bạn có thể đăng nhập với tên đăng nhập/mật khẩu ngay bây giờ.",
     "welcome.cta": "Nhấn vào đây để xác nhận địa chỉ email",
     "invitation.text1": "%1 đã mời bạn tham gia %2",
@@ -14,16 +14,16 @@
     "reset.text2": "Để đặt lại mật khẩu, hãy click vào liên kết sau:",
     "reset.cta": "Click vào đây để khởi tạo lại mật khẩu",
     "reset.notify.subject": "Thay đổi mật khẩu thành công",
-    "reset.notify.text1": "Chúng tôi thông báo với bạn trên %1, mật khẩu của bạn đã thay đổi thành công",
-    "reset.notify.text2": "Nếu bạn không cho phép điều này, vui lòng thông báo cho quản trị viên ngay lập tức",
-    "digest.notifications": "Bạn có thông báo chưa đọc từ %1",
+    "reset.notify.text1": "Xin thông báo với bạn: mật khẩu của bạn trên %1 đã được thay đổi thành công.",
+    "reset.notify.text2": "Nếu bạn không cho phép điều này, vui lòng thông báo cho quản trị viên ngay lập tức.",
+    "digest.notifications": "Bạn có thông báo chưa đọc từ %1:",
     "digest.latest_topics": "Chủ đề mới nhất từ %1",
     "digest.cta": "Click vào đây để truy cập %1",
     "digest.unsub.info": "Tập san này được gửi đến bạn dựa theo cài đặt theo dõi của bạn.",
     "digest.no_topics": "Không có chủ đề nào hoạt động trong %1 ",
-    "digest.day": "day",
-    "digest.week": "week",
-    "digest.month": "month",
+    "digest.day": "ngày",
+    "digest.week": "tuần",
+    "digest.month": "tháng",
     "notif.chat.subject": "Bạn có tin nhắn mới từ %1",
     "notif.chat.cta": "Nhấn vào đây để tiếp tục cuộc hội thoại",
     "notif.chat.unsub.info": "Thông báo tin nhắn này được gửi tới dựa theo cài đặt theo dõi của bạn.",
diff --git a/public/language/vi/error.json b/public/language/vi/error.json
index 2d3f6d74dc..fd5b2a903e 100644
--- a/public/language/vi/error.json
+++ b/public/language/vi/error.json
@@ -1,9 +1,9 @@
 {
     "invalid-data": "Dữ liệu không hợp lệ",
-    "not-logged-in": "Bạn chưa đăng nhập",
+    "not-logged-in": "Có vẻ bạn chưa đăng nhập.",
     "account-locked": "Tài khoản của bạn đang tạm thời bị khóa",
-    "search-requires-login": "Searching requires an account - please login or register.",
-    "invalid-cid": "Danh mục ID không hợp lệ",
+    "search-requires-login": "Bạn cần phải có tài khoản để tìm kiếm - vui lòng đăng nhập hoặc đăng ký.",
+    "invalid-cid": "ID chuyên mục không hợp lệ",
     "invalid-tid": "ID chủ đề không hợp lệ",
     "invalid-pid": "ID bài viết không hợp lệ",
     "invalid-uid": "ID tài khoản không hợp lệ",
@@ -14,19 +14,20 @@
     "invalid-password": "Mật khẩu không hợp lệ",
     "invalid-username-or-password": "Xin hãy nhập cả tên đăng nhập và mật khẩu",
     "invalid-search-term": "Từ khóa không hợp lệ",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "Giá trị trang không hợp lệ, tối thiểu phải là %1 và tối đa là %2",
     "username-taken": "Tên đăng nhập đã tồn tại",
     "email-taken": "Email đã được đăng kí",
     "email-not-confirmed": "Email của bạn chưa được xác nhận, xin hãy nhấn vào đây để xác nhận địa chỉ này là của bạn",
-    "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.",
-    "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email",
-    "email-confirm-failed": "We could not confirm your email, please try again later.",
-    "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.",
+    "email-not-confirmed-chat": "Bạn không được quyền chat nếu email của bạn chưa được xác nhận, vui lòng click vào đây để xác nhận email của bạn.",
+    "no-email-to-confirm": "Diễn đàn này yêu cầu xác nhận email, vui lòng nhấn vào đây để nhập email.",
+    "email-confirm-failed": "Chúng tôi không thể xác nhận email của bạn, vui lòng thử lại sau.",
+    "confirm-email-already-sent": "Email xác nhận đã được gửi, vui lòng chờ %1 phút để yêu cầu gửi lại.",
     "username-too-short": "Tên đăng nhập quá ngắn",
     "username-too-long": "Tên đăng nhập quá dài",
-    "password-too-long": "Password too long",
+    "password-too-long": "Mật khẩu quá dài",
     "user-banned": "Tài khoản bị ban",
-    "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+    "user-too-new": "Rất tiếc, bạn phải chờ %1 giây để đăng bài viết đầu tiên.",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "Danh mục không tồn tại",
     "no-topic": "Chủ đề không tồn tại",
     "no-post": "Bài viết không tồn tại",
@@ -36,65 +37,67 @@
     "no-privileges": "Bạn không đủ quyền để thực thi hành động này",
     "category-disabled": "Danh mục bị khóa",
     "topic-locked": "Chủ đề bị khóa",
-    "post-edit-duration-expired": "You are only allowed to edit posts for %1 second(s) after posting",
-    "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).",
-    "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).",
-    "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).",
-    "title-too-long": "Please enter a shorter title. Titles can't be longer than %1 character(s).",
-    "too-many-posts": "You can only post once every %1 second(s) - please wait before posting again",
-    "too-many-posts-newbie": "As a new user, you can only post once every %1 second(s) until you have earned %2 reputation - please wait before posting again",
-    "tag-too-short": "Please enter a longer tag. Tags should contain at least %1 character(s)",
-    "tag-too-long": "Please enter a shorter tag. Tags can't be longer than %1 character(s)",
-    "not-enough-tags": "Not enough tags. Topics must have at least %1 tag(s)",
-    "too-many-tags": "Too many tags. Topics can't have more than %1 tag(s)",
+    "post-edit-duration-expired": "Bạn chỉ được phép sửa bài viết sau khi đăng %1 giây.",
+    "content-too-short": "Vui lòng nhập một bài viết dài hơn. Bài viết phải có tối thiểu %1 ký tự.",
+    "content-too-long": "Vui lòng nhập một bài viết ngắn hơn. Bài viết chỉ có thể có tối đa %1 ký tự.",
+    "title-too-short": "Vui lòng nhập tiêu đề dài hơn. Tiêu đề phải có tối thiểu %1 ký tự.",
+    "title-too-long": "Vui lòng nhập tiêu đề ngắn hơn. Tiêu đề chỉ có thể có tối đa %1 ký tự.",
+    "too-many-posts": "Bạn chỉ có đăng bài mới mỗi %1 giây - vui lòng đợi để tiếp tục đăng bài.",
+    "too-many-posts-newbie": "Bạn chỉ có thể đăng bài mỗi %1 giây cho đến khi bạn tích luỹ được %2 điểm tín nhiệm - vui lòng đợi để tiếp tục đăng bài.",
+    "tag-too-short": "Vui lòng nhập tag dài hơn. Tag phải có tối thiểu %1 ký tự.",
+    "tag-too-long": "Vui lòng nhập tag ngắn hơn. Tag chỉ có thể có tối đa %1 ký tự.",
+    "not-enough-tags": "Quá ít tag. Chủ đề phải có tối thiểu %1 tag.",
+    "too-many-tags": "Quá nhiều tag. Chủ đề chỉ có thể có tối đa %1 tag.",
     "still-uploading": "Vui lòng chờ upload",
-    "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
-    "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "Bạn đã bấm yêu thích cho bài viết này rồi",
-    "already-unfavourited": "Bạn đã bỏ thích bài này rồi",
+    "file-too-big": "Kích cỡ file được cho phép tối đa là %1 kB - vui lòng tải lên file có dung lượng nhỏ hơn.",
+    "guest-upload-disabled": "Khách (chưa có tài khoản) không có quyền tải lên file.",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "Bạn không thể ban được các admin khác",
-    "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
-    "invalid-image-type": "Invalid image type. Allowed types are: %1",
-    "invalid-image-extension": "Invalid image extension",
-    "invalid-file-type": "Invalid file type. Allowed types are: %1",
+    "cant-remove-last-admin": "Bạn là quản trị viên duy nhất. Hãy cho thành viên khác làm quản trị viên trước khi huỷ bỏ quyền quản trị của bạn.",
+    "invalid-image-type": "Định dạng ảnh không hợp lệ. Những định dạng được cho phép là: %1",
+    "invalid-image-extension": "Định dạng ảnh không hợp lệ",
+    "invalid-file-type": "Định dạng file không hợp lệ. Những định dạng được cho phép là: %1",
     "group-name-too-short": "Tên nhóm quá ngắn",
     "group-already-exists": "Nhóm đã tồn tại",
     "group-name-change-not-allowed": "Không cho phép đổi tên nhóm",
-    "group-already-member": "Already part of this group",
-    "group-not-member": "Not a member of this group",
-    "group-needs-owner": "This group requires at least one owner",
-    "group-already-invited": "This user has already been invited",
-    "group-already-requested": "Your membership request has already been submitted",
+    "group-already-member": "Bạn đã là thành viên của nhóm này.",
+    "group-not-member": "Không phải là thành viên của nhóm này.",
+    "group-needs-owner": "Nhóm này phải có tối thiểu một chủ sỡ hữu",
+    "group-already-invited": "Thành viên này đã được mời",
+    "group-already-requested": "Yêu cầu tham gia của bạn đã được gửi.",
     "post-already-deleted": "Bài viết này đã bị xóa",
     "post-already-restored": "Bài viết này đã được phục hồi",
     "topic-already-deleted": "Chủ đề này đã bị xóa",
     "topic-already-restored": "Chủ đề này đã được phục hồi",
-    "cant-purge-main-post": "You can't purge the main post, please delete the topic instead",
+    "cant-purge-main-post": "Bạn không thể xoá bài viết chính, thay vào đó, vui lòng xoá chủ đề.",
     "topic-thumbnails-are-disabled": "Thumbnails cho chủ đề đã bị tắt",
     "invalid-file": "File không hợp lệ",
     "uploads-are-disabled": "Đã khóa lựa chọn tải lên",
-    "signature-too-long": "Sorry, your signature cannot be longer than %1 character(s).",
-    "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).",
+    "signature-too-long": "Rất tiếc, chữ ký của bạn chỉ có thể có tối đa %1 ký tự.",
+    "about-me-too-long": "Rất tiếc, giới thiệu bản thân của bạn chỉ có thể có tối đa %1 ký tự.",
     "cant-chat-with-yourself": "Bạn không thể chat với chính bạn!",
     "chat-restricted": "Người dùng này đã bật chế độ hạn chế tin nhắn chat. Bạn phải được anh/cô ta follow thì mới có thể gởi tin nhắn đến họ được.",
-    "chat-disabled": "Chat system disabled",
-    "too-many-messages": "You have sent too many messages, please wait awhile.",
-    "invalid-chat-message": "Invalid chat message",
-    "chat-message-too-long": "Chat message is too long",
-    "cant-edit-chat-message": "You are not allowed to edit this message",
-    "cant-remove-last-user": "You can't remove the last user",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "chat-disabled": "Hệ thống chat đã bị vô hiệu hoá",
+    "too-many-messages": "Bạn đã gửi quá nhiều tin nhắn, vui lòng đợi trong giây lát.",
+    "invalid-chat-message": "Tin nhắn không hợp lệ",
+    "chat-message-too-long": "Tin nhắn quá dài",
+    "cant-edit-chat-message": "Bạn không được phép chỉnh sửa tin nhắn này",
+    "cant-remove-last-user": "Bạn không thể xoá thành viên cuối cùng",
+    "cant-delete-chat-message": "Bạn không được phép xoá tin nhắn này",
     "reputation-system-disabled": "Hệ thống tín nhiệm đã bị vô hiệu hóa.",
     "downvoting-disabled": "Downvote đã bị tắt",
     "not-enough-reputation-to-downvote": "Bạn không có đủ phiếu tín nhiệm để downvote bài này",
     "not-enough-reputation-to-flag": "Bạn không đủ tín nhiệm để đánh dấu bài viết này",
-    "already-flagged": "You have already flagged this post",
+    "already-flagged": "Bạn đã gắn cờ cho bài viết này",
     "reload-failed": "NodeBB gặp lỗi trong khi tải lại: \"%1\". NodeBB sẽ tiếp tục hoạt động với dữ liệu trước đó, tuy nhiên bạn nên tháo gỡ những gì bạn vừa thực hiện trước khi tải lại.",
     "registration-error": "Lỗi đăng kí",
-    "parse-error": "Something went wrong while parsing server response",
-    "wrong-login-type-email": "Please use your email to login",
-    "wrong-login-type-username": "Please use your username to login",
-    "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
-    "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "parse-error": "Có gì không ổn khi nhận kết quả từ máy chủ",
+    "wrong-login-type-email": "Xin vui lòng sửa dụng email của bạn để đăng nhập",
+    "wrong-login-type-username": "Vui lòng sử dụng tên đăng nhập của bạn để đăng nhập",
+    "invite-maximum-met": "Bạn đã sử dụng hết số lượng lời mời bạn có thể gửi (%1 đã gửi trên tổng số %2 được cho phép)",
+    "no-session-found": "Không tìm thấy phiên đăng nhập!",
+    "not-in-room": "Thành viên không có trong phòng",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/vi/global.json b/public/language/vi/global.json
index 55d850c49d..e466d74fae 100644
--- a/public/language/vi/global.json
+++ b/public/language/vi/global.json
@@ -2,11 +2,11 @@
     "home": "Trang chủ",
     "search": "Tìm kiếm",
     "buttons.close": "Đóng lại",
-    "403.title": "Từ chối truy cập",
-    "403.message": "You seem to have stumbled upon a page that you do not have access to.",
-    "403.login": "Perhaps you should <a href='%1/login'>try logging in</a>?",
+    "403.title": "Truy cập bị từ chối",
+    "403.message": "Có vẻ bạn đang cố vào một trang mà bạn không có quyền truy cập.",
+    "403.login": "Có lẽ bạn nên <a href='%1/login'>thử đăng nhập</a>?",
     "404.title": "Không tìm thấy",
-    "404.message": "You seem to have stumbled upon a page that does not exist. Return to the <a href='%1/'>home page</a>.",
+    "404.message": "Có vẻ như bạn đang cố vào một trang không tồn tại. Hãy trở lại <a href='%1/'>trang chủ</a>.",
     "500.title": "Lỗi nội bộ",
     "500.message": "Úi chà! Có vẻ như có trục trặc rồi!",
     "register": "Đăng ký",
@@ -22,7 +22,7 @@
     "pagination.out_of": "%1 trong số %2",
     "pagination.enter_index": "Nhập khóa",
     "header.admin": "Quản trị viên",
-    "header.categories": "Categories",
+    "header.categories": "Chuyên mục",
     "header.recent": "Gần đây",
     "header.unread": "Chưa đọc",
     "header.tags": "Tags",
@@ -33,12 +33,12 @@
     "header.notifications": "Thông báo",
     "header.search": "Tìm kiếm",
     "header.profile": "Hồ sơ",
-    "header.navigation": "Navigation",
+    "header.navigation": "Điều hướng",
     "notifications.loading": "Đang tải Thông báo",
-    "chats.loading": "Đang tải phần Chat",
+    "chats.loading": "Đang tải chat",
     "motd.welcome": "Chào mừng bạn tới NodeBB, Platform của tương lai",
     "previouspage": "Trang trước",
-    "nextpage": "Trang kế tiếp",
+    "nextpage": "Trang kế",
     "alert.success": "Thành công",
     "alert.error": "Lỗi",
     "alert.banned": "Bị cấm",
@@ -49,29 +49,29 @@
     "users": "Số người dùng",
     "topics": "Số Chủ đề",
     "posts": "Số bài viết",
-    "best": "Best",
-    "upvoted": "Upvoted",
-    "downvoted": "Downvoted",
-    "views": "Số lượt view",
-    "reputation": "Độ uy tín",
+    "best": "Hay nhất",
+    "upvoted": "Tán thành",
+    "downvoted": "Phản đối",
+    "views": "Lượt xem",
+    "reputation": "Điểm tín nhiệm",
     "read_more": "Đọc thêm",
-    "more": "More",
+    "more": "Xem thêm",
     "posted_ago_by_guest": "Đã viết %1 bởi Khách",
     "posted_ago_by": "Đã viết %1 bởi %2",
     "posted_ago": "Đã viết %1",
-    "posted_in": "posted in %1",
-    "posted_in_by": "posted in %1 by %2",
+    "posted_in": "được đăng trong %1",
+    "posted_in_by": "được đăng trong %1 bởi %2",
     "posted_in_ago": "được đăng trong %1 %2",
     "posted_in_ago_by": "Đã viết trong %1 %2 bởi %3",
     "user_posted_ago": "%1 đã viết %2",
     "guest_posted_ago": "Khách đã viết %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "được chỉnh sửa lần cuối bởi %1",
     "norecentposts": "Không có bài viết nào gần đây",
     "norecenttopics": "Không có chủ đề gần đây",
     "recentposts": "Số bài viết gần đây",
     "recentips": "Các IP vừa mới đăng nhập",
     "away": "Đang đi vắng",
-    "dnd": "Do not disturb",
+    "dnd": "Đừng làm phiền",
     "invisible": "Ẩn",
     "offline": "Đang offline",
     "email": "Email",
@@ -84,7 +84,11 @@
     "follow": "Theo dõi",
     "unfollow": "Huỷ theo dõi",
     "delete_all": "Xóa hết",
-    "map": "Map",
-    "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "map": "Bản đồ",
+    "sessions": "Phiên đăng nhập",
+    "ip_address": "Địa chỉ IP",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/vi/groups.json b/public/language/vi/groups.json
index 7ce867cb97..1bb89ad728 100644
--- a/public/language/vi/groups.json
+++ b/public/language/vi/groups.json
@@ -6,47 +6,49 @@
     "no_groups_found": "Không có nhóm nào để hiển thị",
     "pending.accept": "Chấp nhận",
     "pending.reject": "Từ chối",
-    "pending.accept_all": "Accept All",
-    "pending.reject_all": "Reject All",
-    "pending.none": "There are no pending members at this time",
-    "invited.none": "There are no invited members at this time",
-    "invited.uninvite": "Rescind Invitation",
-    "invited.search": "Search for a user to invite to this group",
-    "invited.notification_title": "You have been invited to join <strong>%1</strong>",
-    "request.notification_title": "Group Membership Request from <strong>%1</strong>",
-    "request.notification_text": "<strong>%1</strong> has requested to become a member of <strong>%2</strong>",
+    "pending.accept_all": "Chấp nhận tất cả",
+    "pending.reject_all": "Từ chối tất cả",
+    "pending.none": "Hiện tại không có thành viên nào đang chờ được duyệt để tham gia nhóm",
+    "invited.none": "Hiện tại không có thành viên nào được mời tham gia nhóm",
+    "invited.uninvite": "Từ chối lời mời",
+    "invited.search": "Tìm kiếm thành viên để mời vào nhóm",
+    "invited.notification_title": "Bạn đã được mời tham gia <strong>%1</strong>",
+    "request.notification_title": "Yêu cầu tham gia nhóm từ <strong>%1</strong>",
+    "request.notification_text": "<strong>%1</strong> yêu cầu chấp nhận để trở thành thành viên của <strong>%2</strong>",
     "cover-save": "Lưu",
     "cover-saving": "Đang lưu",
     "details.title": "Thông tin nhóm",
     "details.members": "Danh sách thành viên",
-    "details.pending": "Pending Members",
-    "details.invited": "Invited Members",
+    "details.pending": "Thành viên đang chờ trả lời",
+    "details.invited": "Thành viên đã được mời",
     "details.has_no_posts": "Nhóm thành viên này chưa viết bài viết nào.",
     "details.latest_posts": "Bài mới nhất",
-    "details.private": "Private",
-    "details.disableJoinRequests": "Disable join requests",
-    "details.grant": "Grant/Rescind Ownership",
-    "details.kick": "Kick",
-    "details.owner_options": "Group Administration",
+    "details.private": "Riêng tư",
+    "details.disableJoinRequests": "Vô hiệu hóa yêu cầu tham gia",
+    "details.grant": "Cấp/Huỷ quyền trưởng nhóm",
+    "details.kick": "Đá ra",
+    "details.owner_options": "Quản trị nhóm",
     "details.group_name": "Tên nhóm",
-    "details.member_count": "Member Count",
-    "details.creation_date": "Creation Date",
-    "details.description": "Description",
-    "details.badge_preview": "Badge Preview",
-    "details.change_icon": "Change Icon",
-    "details.change_colour": "Change Colour",
-    "details.badge_text": "Badge Text",
-    "details.userTitleEnabled": "Show Badge",
-    "details.private_help": "If enabled, joining of groups requires approval from a group owner",
-    "details.hidden": "Hidden",
-    "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
-    "details.delete_group": "Delete Group",
+    "details.member_count": "Số thành viên",
+    "details.creation_date": "Ngày mở nhóm",
+    "details.description": "Miêu tả",
+    "details.badge_preview": "Xem thử huy hiệu",
+    "details.change_icon": "Đổi icon",
+    "details.change_colour": "Đổi màu",
+    "details.badge_text": "Huy hiệu",
+    "details.userTitleEnabled": "Hiện huy hiệu",
+    "details.private_help": "Nếu bật, để tham gia nhóm cần phải có sự chấp thuận từ trưởng nhóm",
+    "details.hidden": "Đã ẩn",
+    "details.hidden_help": "Nếu bật, nhóm này sẽ không được hiện thị trong danh sách nhóm, và thành viên phải được mời để tham gia",
+    "details.delete_group": "Xoá nhóm",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "Thông tin nhóm đã được cập nhật",
-    "event.deleted": "The group \"%1\" has been deleted",
-    "membership.accept-invitation": "Accept Invitation",
-    "membership.invitation-pending": "Invitation Pending",
-    "membership.join-group": "Join Group",
-    "membership.leave-group": "Leave Group",
-    "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "event.deleted": "Nhóm \"%1\" đã bị xoá",
+    "membership.accept-invitation": "Chấp nhận lời mời",
+    "membership.invitation-pending": "Lời mời đang chờ trả lời",
+    "membership.join-group": "Tham gia nhóm",
+    "membership.leave-group": "Rời khỏi nhóm",
+    "membership.reject": "Từ chối",
+    "new-group.group_name": "Tên nhóm",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/vi/language.json b/public/language/vi/language.json
index fbe233f267..f0787f748a 100644
--- a/public/language/vi/language.json
+++ b/public/language/vi/language.json
@@ -1,5 +1,5 @@
 {
-    "name": "Tiếng Anh",
+    "name": "Tiếng Việt",
     "code": "vi",
     "dir": "ltr"
 }
\ No newline at end of file
diff --git a/public/language/vi/login.json b/public/language/vi/login.json
index 4ae9230b8a..01b7f2dc96 100644
--- a/public/language/vi/login.json
+++ b/public/language/vi/login.json
@@ -1,10 +1,10 @@
 {
-    "username-email": "Username / Email",
-    "username": "Username",
+    "username-email": "Tên đăng nhập / Email",
+    "username": "Tên đăng nhập",
     "email": "Email",
-    "remember_me": "Ghi nhớ tôi?",
+    "remember_me": "Ghi nhớ?",
     "forgot_password": "Quên mật khẩu?",
-    "alternative_logins": "Đăng nhập bằng tên khác",
+    "alternative_logins": "Đăng nhập bằng tài khoản khác",
     "failed_login_attempt": "Đăng nhập thất bại, xin hãy thử lại",
     "login_successful": "Bạn đã đăng nhập thành công!",
     "dont_have_account": "Chưa có tài khoản?"
diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json
index f0ad033020..0f5ba2fe46 100644
--- a/public/language/vi/modules.json
+++ b/public/language/vi/modules.json
@@ -1,13 +1,14 @@
 {
     "chat.chatting_with": "Chat với <span id=\"chat-with-name\"></span>",
     "chat.placeholder": "Nhập tin nhắn ở đây, nhấn enter để gửi",
-    "chat.send": "Gửi đi",
+    "chat.send": "Gửi",
     "chat.no_active": "Bạn hiện giờ không có cuộc chat nào",
-    "chat.user_typing": "%1b đang gõ",
+    "chat.user_typing": "%1b đang gõ...",
     "chat.user_has_messaged_you": "%1 đã gửi tin cho bạn.",
-    "chat.see_all": "See all chats",
+    "chat.see_all": "Xem tất cả",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "Hãy chọn 1 tài khoản để xem lịch sử chat",
-    "chat.no-users-in-room": "No users in this room",
+    "chat.no-users-in-room": "Không có người nào trong phòng này.",
     "chat.recent-chats": "Vừa chat",
     "chat.contacts": "Liên hệ",
     "chat.message-history": "Lịch sử tin nhắn",
@@ -16,22 +17,22 @@
     "chat.seven_days": "7 ngày",
     "chat.thirty_days": "30 ngày",
     "chat.three_months": "3 tháng",
-    "chat.delete_message_confirm": "Are you sure you wish to delete this message?",
-    "chat.roomname": "Chat Room %1",
-    "chat.add-users-to-room": "Add users to room",
-    "composer.compose": "Compose",
-    "composer.show_preview": "Show Preview",
-    "composer.hide_preview": "Hide Preview",
+    "chat.delete_message_confirm": "Bạn có chắc chắn bạn muốn xoá tin nhắn này chứ?",
+    "chat.roomname": "Phòng chat %1",
+    "chat.add-users-to-room": "Thêm người vào phòng",
+    "composer.compose": "Soạn thảo",
+    "composer.show_preview": "Hiện Xem trước",
+    "composer.hide_preview": "Ẩn Xem trước",
     "composer.user_said_in": "%1 đã nói trong %2:",
     "composer.user_said": "%1 đã nói:",
     "composer.discard": "Bạn có chắc chắn hủy bỏ bài viết này?",
-    "composer.submit_and_lock": "Submit and Lock",
-    "composer.toggle_dropdown": "Toggle Dropdown",
-    "composer.uploading": "Uploading %1",
+    "composer.submit_and_lock": "Đăng và Khoá",
+    "composer.toggle_dropdown": "Đóng/mở dropdown",
+    "composer.uploading": "Đang tải lên %1",
     "bootbox.ok": "OK",
-    "bootbox.cancel": "Cancel",
-    "bootbox.confirm": "Confirm",
-    "cover.dragging_title": "Cover Photo Positioning",
-    "cover.dragging_message": "Drag the cover photo to the desired position and click \"Save\"",
-    "cover.saved": "Cover photo image and position saved"
+    "bootbox.cancel": "Huỷ bỏ",
+    "bootbox.confirm": "Xác nhận",
+    "cover.dragging_title": "Điều chỉnh vị trí ảnh cover",
+    "cover.dragging_message": "Kéo ảnh cover vào vị trí bạn ưng ý và nhấn \"Lưu\"",
+    "cover.saved": "Ảnh cover và vị trí đã được lưu"
 }
\ No newline at end of file
diff --git a/public/language/vi/notifications.json b/public/language/vi/notifications.json
index 922275e1c3..1520437ea9 100644
--- a/public/language/vi/notifications.json
+++ b/public/language/vi/notifications.json
@@ -1,35 +1,36 @@
 {
     "title": "Thông báo",
     "no_notifs": "Bạn không có thông báo nào mới",
-    "see_all": "See all notifications",
+    "see_all": "Xem tất cả thông báo",
     "mark_all_read": "Đánh dấu đã xem tất cả thông báo",
     "back_to_home": "Quay lại %1",
     "outgoing_link": "Liên kết ngoài",
-    "outgoing_link_message": "You are now leaving %1",
+    "outgoing_link_message": "Bạn đang rời khỏi %1",
     "continue_to": "Tiếp tục tới %1",
     "return_to": "Quay lại %1",
     "new_notification": "Thông báo mới",
     "you_have_unread_notifications": "Bạn có thông báo chưa đọc",
     "new_message_from": "Tin nhắn mới từ <strong>%1</strong>",
     "upvoted_your_post_in": "<strong>%1</strong> đã bình chọn bài của bạn trong <strong>%2</strong>.",
-    "upvoted_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have upvoted your post in <strong>%3</strong>.",
-    "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
-    "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
-    "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> đã thích bài của bạn trong <strong>%2</strong>.",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "upvoted_your_post_in_dual": "<strong>%1</strong> và <strong>%2</strong> đã tán thành với bài viết của bạn trong <strong>%3</strong>.",
+    "upvoted_your_post_in_multiple": "<strong>%1</strong> và %2 others đã tán thành với bài viết của bạn trong <strong>%3</strong>.",
+    "moved_your_post": "<strong>%1</strong> đã chuyển bài viết của bạn tới <strong>%2</strong>",
+    "moved_your_topic": "<strong>%1</strong> đã chuyển <strong>%2</strong>",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> gắn cờ 1 bài trong <strong>%2</strong>",
-    "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
-    "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
+    "user_flagged_post_in_dual": "<strong>%1</strong> và <strong>%2</strong> đã gắn cờ một bài viết trong <strong>%3</strong>",
+    "user_flagged_post_in_multiple": "<strong>%1</strong> và %2 người khác đã gắn cờ bài viết của bạn trong <strong>%3</strong>",
     "user_posted_to": "<strong>%1</strong> đã trả lời <strong>%2</strong>",
-    "user_posted_to_dual": "<strong>%1</strong> and <strong>%2</strong> have posted replies to: <strong>%3</strong>",
-    "user_posted_to_multiple": "<strong>%1</strong> and %2 others have posted replies to: <strong>%3</strong>",
+    "user_posted_to_dual": "<strong>%1</strong> và <strong>%2</strong> đã trả lời: <strong>%3</strong>",
+    "user_posted_to_multiple": "<strong>%1</strong> và %2 người khác đã trả lời: <strong>%3</strong>",
     "user_posted_topic": "<strong>%1</strong> đã gởi chủ đề mới ở <strong>%2</strong>",
     "user_started_following_you": "<strong>%1</strong> đã theo dõi bạn.",
-    "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
-    "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
-    "new_register": "<strong>%1</strong> sent a registration request.",
+    "user_started_following_you_dual": "<strong>%1</strong> và <strong>%2</strong>  đã bắt đầu theo dõi bạn.",
+    "user_started_following_you_multiple": "<strong>%1</strong> và %2 người khác đã bắt đầu theo dõi bạn.",
+    "new_register": "<strong>%1</strong> đã gửi một yêu cầu tham gia.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "Đã xác nhận email",
     "email-confirmed-message": "Cảm ơn bạn đã xác nhận địa chỉ email của bạn. Tài khoản của bạn đã được kích hoạt đầy đủ.",
     "email-confirm-error-message": "Đã có lỗi khi xác nhận địa chỉ email. Có thể đoạn mã không đúng hoặc đã hết hạn.",
diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json
index ea0217f9aa..13b6ab018d 100644
--- a/public/language/vi/pages.json
+++ b/public/language/vi/pages.json
@@ -1,45 +1,46 @@
 {
     "home": "Trang chủ",
     "unread": "Chủ đề chưa đọc",
-    "popular-day": "Popular topics today",
-    "popular-week": "Popular topics this week",
-    "popular-month": "Popular topics this month",
-    "popular-alltime": "All time popular topics",
+    "popular-day": "Chủ đề nổi bật hôm nay",
+    "popular-week": "Chủ đề nội bật tuần này",
+    "popular-month": "Chủ đề nổi bật tháng này",
+    "popular-alltime": "Chủ đề nổi bật mọi thời đại",
     "recent": "Chủ đề gần đây",
-    "flagged-posts": "Flagged Posts",
-    "users/online": "Online Users",
-    "users/latest": "Latest Users",
-    "users/sort-posts": "Users with the most posts",
-    "users/sort-reputation": "Users with the most reputation",
-    "users/banned": "Banned Users",
-    "users/search": "User Search",
+    "flagged-posts": "Bài viết bị gắn cờ",
+    "users/online": "Thành viên đang online",
+    "users/latest": "Thành viên mới nhất",
+    "users/sort-posts": "Thành viên có nhiều bài đăng nhất",
+    "users/sort-reputation": "Thành viên có điểm tín nhiệm cao nhất",
+    "users/banned": "Thành viên đã bị cấm",
+    "users/search": "Tìm kiếm thành viên",
     "notifications": "Thông báo",
-    "tags": "Tags",
-    "tag": "Topics tagged under \"%1\"",
-    "register": "Register an account",
-    "login": "Login to your account",
-    "reset": "Reset your account password",
-    "categories": "Categories",
-    "groups": "Groups",
-    "group": "%1 group",
-    "chats": "Chats",
-    "chat": "Chatting with %1",
-    "account/edit": "Editing \"%1\"",
-    "account/edit/password": "Editing password of \"%1\"",
-    "account/edit/username": "Editing username of \"%1\"",
-    "account/edit/email": "Editing email of \"%1\"",
-    "account/following": "People %1 follows",
-    "account/followers": "People who follow %1",
-    "account/posts": "Posts made by %1",
-    "account/topics": "Topics created by %1",
-    "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
-    "account/settings": "User Settings",
-    "account/watched": "Topics watched by %1",
-    "account/upvoted": "Posts upvoted by %1",
-    "account/downvoted": "Posts downvoted by %1",
-    "account/best": "Best posts made by %1",
+    "tags": "Tag",
+    "tag": "Chủ đề được gắn tag \"%1\"",
+    "register": "Đăng ký một tài khoản mới",
+    "login": "Đăng nhập vào tài khoản của bạn",
+    "reset": "Phục hồi mật khẩu của bạn",
+    "categories": "Chuyên mục",
+    "groups": "Nhóm",
+    "group": "Nhóm %1",
+    "chats": "Chat",
+    "chat": "Chat với %1",
+    "account/edit": "Chỉnh sửa \"%1\"",
+    "account/edit/password": "Chỉnh sửa mật khẩu của \"%1\"",
+    "account/edit/username": "Chỉnh sửa tên đăng nhập của \"%1\"",
+    "account/edit/email": "Chỉnh sửa email của \"%1\"",
+    "account/following": "Thành viên %1 đang theo dõi",
+    "account/followers": "Thành viên đang theo dõi %1",
+    "account/posts": "Bài viết được đăng bởi %1",
+    "account/topics": "Chủ đề được tạo bởi %1",
+    "account/groups": "Nhóm của %1",
+    "account/favourites": "%1's Bookmarked Posts",
+    "account/settings": "Thiết lập",
+    "account/watched": "Chủ đề %1 đang theo dõi",
+    "account/upvoted": "Bài viết %1 tán thành",
+    "account/downvoted": "Bài viết %1 phản đối",
+    "account/best": "Bài viết hay nhất của %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "%1 đang được bảo trì. Xin vui lòng quay lại sau.",
     "maintenance.messageIntro": "Ban quản lí để lại lời nhắn sau:",
-    "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
+    "throttled.text": "%1 hiện đang bị quá tải. Vui lòng quay lại sau."
 }
\ No newline at end of file
diff --git a/public/language/vi/recent.json b/public/language/vi/recent.json
index 12a131279d..99766aefda 100644
--- a/public/language/vi/recent.json
+++ b/public/language/vi/recent.json
@@ -5,15 +5,15 @@
     "month": "Tháng",
     "year": "Năm",
     "alltime": "Tất cả thời gian",
-    "no_recent_topics": "Không có chủ đề nào gần đây",
-    "no_popular_topics": "There are no popular topics.",
+    "no_recent_topics": "Không có chủ đề nào gần đây.",
+    "no_popular_topics": "Không có chủ đề nào phổ biến.",
     "there-is-a-new-topic": "Có chủ đề mới",
     "there-is-a-new-topic-and-a-new-post": "Có chủ đề mới và bài viết mới",
-    "there-is-a-new-topic-and-new-posts": "There is a new topic and %1 new posts.",
-    "there-are-new-topics": "There are %1 new topics.",
-    "there-are-new-topics-and-a-new-post": "There are %1 new topics and a new post.",
-    "there-are-new-topics-and-new-posts": "There are %1 new topics and %2 new posts.",
+    "there-is-a-new-topic-and-new-posts": "Có 1 chủ đề mới và %1 bài viết mới.",
+    "there-are-new-topics": "Có %1 chủ đề mới.",
+    "there-are-new-topics-and-a-new-post": "Có %1 chủ đề mới và 1 bài viết mới.",
+    "there-are-new-topics-and-new-posts": "Có %1 chủ đề mới và %2 bài viết mới.",
     "there-is-a-new-post": "Có bài viết mới",
-    "there-are-new-posts": "There are %1 new posts.",
-    "click-here-to-reload": "Nhấn vào đây để tải lại"
+    "there-are-new-posts": "Có %1 bài viết mới.",
+    "click-here-to-reload": "Nhấn vào đây để tải lại."
 }
\ No newline at end of file
diff --git a/public/language/vi/register.json b/public/language/vi/register.json
index b24f67d78e..91e70a7d0b 100644
--- a/public/language/vi/register.json
+++ b/public/language/vi/register.json
@@ -1,12 +1,12 @@
 {
     "register": "Đăng ký",
-    "help.email": "Theo mặc định, Email của bạn sẽ ở dạng ẩn và public sẽ không thấy được",
+    "help.email": "Theo mặc định, Email của bạn sẽ được ẩn và public sẽ không thấy được",
     "help.username_restrictions": "Một tên truy cập duy nhất có từ %1 đến %2 ký tự. Những người khác có thể nhắc đến bạn bằng @<span id='yourUsername'>tên truy cập</span>.",
     "help.minimum_password_length": "Mật khẩu của bạn phải có ít nhất %1 ký tự",
     "email_address": "Địa chỉ Email",
     "email_address_placeholder": "Nhập địa chỉ Email",
-    "username": "Tên truy cập",
-    "username_placeholder": "Nhập tên truy cập",
+    "username": "Tên đăng nhập",
+    "username_placeholder": "Nhập tên đăng nhập",
     "password": "Mật khẩu",
     "password_placeholder": "Nhập mật khẩu",
     "confirm_password": "Xác nhận mật khẩu",
@@ -15,5 +15,5 @@
     "alternative_registration": "Đăng ký tài khoản khác",
     "terms_of_use": "Điều khoản sử dụng",
     "agree_to_terms_of_use": "Tôi đồng ý với các điều khoản sử dụng",
-    "registration-added-to-queue": "Your registration has been added to the approval queue. You will receive an email when it is accepted by an administrator."
+    "registration-added-to-queue": "Yêu cầu đăng ký của bạn đang chờ được chấp thuận. Bạn sẽ nhận được email khi tài khoản của bạn đã được chấp thuận bởi quản trị viên."
 }
\ No newline at end of file
diff --git a/public/language/vi/reset_password.json b/public/language/vi/reset_password.json
index 7e3ce09565..53e9e4060c 100644
--- a/public/language/vi/reset_password.json
+++ b/public/language/vi/reset_password.json
@@ -11,7 +11,7 @@
     "enter_email_address": "Nhập địa chỉ Email",
     "password_reset_sent": "Đã gửi mật khẩu được thiết lập lại",
     "invalid_email": "Email không đúng / Email không tồn tại!",
-    "password_too_short": "The password entered is too short, please pick a different password.",
-    "passwords_do_not_match": "The two passwords you've entered do not match.",
-    "password_expired": "Your password has expired, please choose a new password"
+    "password_too_short": "Mật khẩu bạn nhập quá ngắn, vui lòng chọn một mật khẩu khác.",
+    "passwords_do_not_match": "Hai mật khẩu bạn nhập không trùng khớp với nhau.",
+    "password_expired": "Mật khẩu của bạn đã hết hạn, vui lòng chọn một mật khẩu mới."
 }
\ No newline at end of file
diff --git a/public/language/vi/search.json b/public/language/vi/search.json
index 312c315c73..6e3244bf2d 100644
--- a/public/language/vi/search.json
+++ b/public/language/vi/search.json
@@ -1,40 +1,40 @@
 {
     "results_matching": "%1 kết quả(s) trùng với \"%2\", (%3 giây)",
-    "no-matches": "No matches found",
-    "advanced-search": "Advanced Search",
-    "in": "In",
-    "titles": "Titles",
-    "titles-posts": "Titles and Posts",
-    "posted-by": "Posted by",
-    "in-categories": "In Categories",
-    "search-child-categories": "Search child categories",
-    "reply-count": "Reply Count",
-    "at-least": "At least",
-    "at-most": "At most",
-    "post-time": "Post time",
-    "newer-than": "Newer than",
-    "older-than": "Older than",
-    "any-date": "Any date",
-    "yesterday": "Yesterday",
-    "one-week": "One week",
-    "two-weeks": "Two weeks",
-    "one-month": "One month",
-    "three-months": "Three months",
-    "six-months": "Six months",
-    "one-year": "One year",
-    "sort-by": "Sort by",
-    "last-reply-time": "Last reply time",
-    "topic-title": "Topic title",
-    "number-of-replies": "Number of replies",
-    "number-of-views": "Number of views",
-    "topic-start-date": "Topic start date",
-    "username": "Username",
-    "category": "Category",
-    "descending": "In descending order",
-    "ascending": "In ascending order",
-    "save-preferences": "Save preferences",
-    "clear-preferences": "Clear preferences",
-    "search-preferences-saved": "Search preferences saved",
-    "search-preferences-cleared": "Search preferences cleared",
-    "show-results-as": "Show results as"
+    "no-matches": "Không tìm thấy kết quả phù hợp",
+    "advanced-search": "Tìm kiếm nâng cao",
+    "in": "Trong",
+    "titles": "Các tựa đề",
+    "titles-posts": "Tựa đề và Bài viết",
+    "posted-by": "Đăng bởi",
+    "in-categories": "Nằm trong chuyên mục",
+    "search-child-categories": "Tìm kiếm chuyên mục con",
+    "reply-count": "Số lượt trả lời",
+    "at-least": "Tối thiểu",
+    "at-most": "Tối đa",
+    "post-time": "Thời điểm đăng bài",
+    "newer-than": "Mới hơn",
+    "older-than": "Cũ hơn",
+    "any-date": "Bất kì ngày nào",
+    "yesterday": "Hôm qua",
+    "one-week": "Một tuần",
+    "two-weeks": "Hai tuần",
+    "one-month": "Một tháng",
+    "three-months": "Ba tháng",
+    "six-months": "Sáu tháng",
+    "one-year": "Một năm",
+    "sort-by": "Sắp xếp theo",
+    "last-reply-time": "Thời điểm trả lời lần cuối",
+    "topic-title": "Tựa đề chủ đề",
+    "number-of-replies": "Số lượt trả lời",
+    "number-of-views": "Số lượt xem",
+    "topic-start-date": "Ngày bắt đầu chủ đề",
+    "username": "Tên đăng nhập",
+    "category": "Chuyên mục ",
+    "descending": "Theo thứ tự giảm dần",
+    "ascending": "Theo thứ tự tăng dần",
+    "save-preferences": "Lưu tuỳ chọn",
+    "clear-preferences": "Xoá tuỳ chọn",
+    "search-preferences-saved": "Tìm kiếm tuỳ chọn đã lưu",
+    "search-preferences-cleared": "Tìm kiếm tuỳ chọn đã xoá",
+    "show-results-as": "Hiện thị kết quả theo"
 }
\ No newline at end of file
diff --git a/public/language/vi/tags.json b/public/language/vi/tags.json
index 7b8931883f..f0bd336781 100644
--- a/public/language/vi/tags.json
+++ b/public/language/vi/tags.json
@@ -1,7 +1,7 @@
 {
     "no_tag_topics": "Không có bài viết nào với thẻ này.",
     "tags": "Thẻ",
-    "enter_tags_here": "Enter tags here, between %1 and %2 characters each.",
-    "enter_tags_here_short": "Tên thẻ...",
+    "enter_tags_here": "Nhập thẻ ở đây, mỗi thẻ phải có từ %1 tới %2 ký tự.",
+    "enter_tags_here_short": "Nhập thẻ...",
     "no_tags": "Chưa có thẻ nào."
 }
\ No newline at end of file
diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json
index 0248c39aad..683acae9fa 100644
--- a/public/language/vi/topic.json
+++ b/public/language/vi/topic.json
@@ -13,20 +13,20 @@
     "notify_me": "Được thông báo khi có trả lời mới trong chủ đề này",
     "quote": "Trích dẫn",
     "reply": "Trả lời",
-    "reply-as-topic": "Reply as topic",
+    "reply-as-topic": "Trả lời dưới dạng chủ đề",
     "guest-login-reply": "Hãy đăng nhập để trả lời",
     "edit": "Chỉnh sửa",
     "delete": "Xóa",
     "purge": "Xóa hẳn",
     "restore": "Phục hồi",
     "move": "Chuyển đi",
-    "fork": "Fork",
-    "link": "đường dẫn",
+    "fork": "Tạo bản sao",
+    "link": "Đường dẫn",
     "share": "Chia sẻ",
     "tools": "Công cụ",
-    "flag": "Flag",
+    "flag": "Gắn cờ",
     "locked": "Khóa",
-    "bookmark_instructions": "Click here to return to the last unread post in this thread.",
+    "bookmark_instructions": "Nhấn vào đây để trở lại bài viết cuối cùng mà bạn chưa đọc trong chủ đề này.",
     "flag_title": "Flag bài viết này để chỉnh sửa",
     "flag_success": "Chủ đề này đã được flag để chỉnh sửa",
     "deleted_message": "Chủ đề này đã bị xóa. Chỉ ban quản trị mới xem được.",
@@ -34,8 +34,8 @@
     "not_following_topic.message": "Bạn sẽ không còn nhận được thông báo từ chủ đề này",
     "login_to_subscribe": "Xin hãy đăng ký hoặc đăng nhập để theo dõi topic này",
     "markAsUnreadForAll.success": "Chủ đề đã được đánh dấu là chưa đọc toàn bộ",
-    "mark_unread": "Mark unread",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread": "Đánh dấu chưa đọc",
+    "mark_unread.success": "Chủ đề đã được đánh dấu chưa đọc.",
     "watch": "Theo dõi",
     "unwatch": "Ngừng theo dõi",
     "watch.title": "Được thông báo khi có trả lời mới trong chủ đề này",
@@ -49,9 +49,9 @@
     "thread_tools.unlock": "Mở khóa chủ đề",
     "thread_tools.move": "Chuyển chủ đề",
     "thread_tools.move_all": "Chuyển tất cả",
-    "thread_tools.fork": "Fork chủ đề",
+    "thread_tools.fork": "Tạo bản sao chủ đề",
     "thread_tools.delete": "Xóa chủ đề",
-    "thread_tools.delete-posts": "Delete Posts",
+    "thread_tools.delete-posts": "Xoá bài viết",
     "thread_tools.delete_confirm": "Bạn có muốn xóa chủ đề này?",
     "thread_tools.restore": "Phục hồi chủ đề",
     "thread_tools.restore_confirm": "Bạn có muốn phục hồi chủ đề này?",
@@ -63,25 +63,25 @@
     "post_purge_confirm": "Bạn có chắc muốn xóa hẳn bài này?",
     "load_categories": "Đang tải các phần mục",
     "disabled_categories_note": "Các phần mục bị khóa đã được đánh xám",
-    "confirm_move": "Chuyển",
-    "confirm_fork": "Fork",
-    "favourite": "Yêu thích",
-    "favourites": "Đang yêu thích",
-    "favourites.has_no_favourites": "Bạn đang không có yêu thích nào. Hãy yêu thích một vài bài viết để thấy được chúng tại đây!",
+    "confirm_move": "Di chuyển",
+    "confirm_fork": "Tạo bảo sao",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "Tải thêm các bài gửi khác",
     "move_topic": "Chuyển chủ đề",
     "move_topics": "Di chuyển chủ đề",
     "move_post": "Chuyển bài gửi",
     "post_moved": "Đã chuyển bài gửi!",
-    "fork_topic": "Fork chủ đề",
+    "fork_topic": "Tạo bản sao chủ đề",
     "topic_will_be_moved_to": "Chủ đề này sẽ được chuyển tới phần mục",
-    "fork_topic_instruction": "Nhấp vào bài gửi mà bạn muốn fork",
+    "fork_topic_instruction": "Chọn vào bài gửi mà bạn muốn fork",
     "fork_no_pids": "Chưa chọn bài gửi nào!",
     "fork_success": "Tạo bản sao thành công! Nhấn vào đây để chuyển tới chủ đề vừa tạo.",
-    "delete_posts_instruction": "Click the posts you want to delete/purge",
+    "delete_posts_instruction": "Chọn những bài viết bạn muốn xoá",
     "composer.title_placeholder": "Nhập tiêu đề cho chủ đề của bạn tại đây...",
     "composer.handle_placeholder": "Tên",
-    "composer.discard": "Loại bỏ",
+    "composer.discard": "Huỷ bỏ",
     "composer.submit": "Gửi",
     "composer.replying_to": "Đang trả lời %1",
     "composer.new_topic": "Chủ đề mới",
@@ -101,12 +101,12 @@
     "newest_to_oldest": "Mới đến cũ",
     "most_votes": "Bình chọn nhiều nhất",
     "most_posts": "Có nhiều bài viết nhất",
-    "stale.title": "Create new topic instead?",
-    "stale.warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?",
-    "stale.create": "Create a new topic",
-    "stale.reply_anyway": "Reply to this topic anyway",
+    "stale.title": "Tạo chủ đề mới?",
+    "stale.warning": "Chủ đề bạn đang trả lời đã khá cũ. Bạn có muốn tạo chủ đề mới, và liên kết với chủ đề hiện tại trong bài viết trả lời của bạn?",
+    "stale.create": "Tạo chủ đề mới",
+    "stale.reply_anyway": "Trả lời chủ đề này",
     "link_back": "Re: [%1](%2)",
     "spam": "Spam",
-    "offensive": "Offensive",
-    "custom-flag-reason": "Enter a flagging reason"
+    "offensive": "Phản cảm",
+    "custom-flag-reason": "Nhập lý do bị gắn cờ"
 }
\ No newline at end of file
diff --git a/public/language/vi/unread.json b/public/language/vi/unread.json
index 049d36dba3..14233ae900 100644
--- a/public/language/vi/unread.json
+++ b/public/language/vi/unread.json
@@ -5,6 +5,6 @@
     "mark_as_read": "Đánh dấu đã đọc",
     "selected": "Đã chọn",
     "all": "Tất cả",
-    "all_categories": "All categories",
+    "all_categories": "Tất cả chuyên mục",
     "topics_marked_as_read.success": "Chủ đề được đánh dấu đã đọc"
 }
\ No newline at end of file
diff --git a/public/language/vi/uploads.json b/public/language/vi/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/vi/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/vi/user.json b/public/language/vi/user.json
index a6e095cadb..c98cd1b9f9 100644
--- a/public/language/vi/user.json
+++ b/public/language/vi/user.json
@@ -6,13 +6,13 @@
     "postcount": "Số bài viết",
     "email": "Email",
     "confirm_email": "Xác nhận email",
-    "ban_account": "Ban Account",
-    "ban_account_confirm": "Do you really want to ban this user?",
-    "unban_account": "Unban Account",
+    "ban_account": "Cấm thành viên",
+    "ban_account_confirm": "Bạn có chắc bạn muốn cấm thành viên này?",
+    "unban_account": "Bỏ cấm thành viên",
     "delete_account": "Xóa tài khoản",
     "delete_account_confirm": "Bạn có chắc muốn xóa tài khoản của mình? <br /><strong> Hành động này không thể khôi phục và bạn sẽ mất toàn bộ dữ liệu</strong><br /><br /> Hãy nhập tên đăng nhập để xác nhận.",
-    "delete_this_account_confirm": "Are you sure you want to delete this account? <br /><strong>This action is irreversible and you will not be able to recover any data</strong><br /><br />",
-    "account-deleted": "Account deleted",
+    "delete_this_account_confirm": "Bạn có chắc bạn muốn xoá tài khoản này chứ? <br /><strong>Tác vụ này không thể đảo ngược và bạn sẽ không thể phục hồi lại được dữ liệu đã xoá</strong><br /><br />",
+    "account-deleted": "Tài khoản đã bị xoá",
     "fullname": "Tên đầy đủ",
     "website": "Website",
     "location": "Địa điểm",
@@ -22,24 +22,25 @@
     "profile": "Hồ sơ",
     "profile_views": "Số lượt người ghé thăm",
     "reputation": "Mức uy tín",
-    "favourites": "Yêu thích",
+    "favourites": "Bookmarks",
     "watched": "Đã theo dõi",
     "followers": "Số người theo dõi",
     "following": "Đang theo dõi",
-    "aboutme": "About me",
+    "aboutme": "Giới thiệu bản thân",
     "signature": "Chữ ký",
     "birthday": "Ngày sinh ",
     "chat": "Chat",
-    "chat_with": "Chat with %1",
+    "chat_with": "Chat với %1",
     "follow": "Theo dõi",
     "unfollow": "Hủy theo dõi",
     "more": "Xem thêm",
     "profile_update_success": "Hồ sơ đã được cập nhật thành công",
     "change_picture": "Thay đổi hình ảnh",
-    "change_username": "Change Username",
-    "change_email": "Change Email",
+    "change_username": "Đổi tên đăng nhập",
+    "change_email": "Đổi email",
     "edit": "Chỉnh sửa",
-    "default_picture": "Default Icon",
+    "edit-profile": "Edit Profile",
+    "default_picture": "Icon mặc định",
     "uploaded_picture": "Ảnh đã tải lên",
     "upload_new_picture": "Tải lên ảnh mới",
     "upload_new_picture_from_url": "Tải ảnh mới từ URL",
@@ -54,11 +55,12 @@
     "confirm_password": "Xác nhận mật khẩu",
     "password": "Mật khẩu",
     "username_taken_workaround": "Tên truy cập này đã tồn tại, vì vậy chúng tôi đã sửa đổi nó một chút. Tên truy cập của bạn giờ là <strong>%1</strong>",
-    "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_username": "Mật khẩu của bạn trùng với tên đăng nhập, vui lòng chọn một mật khẩu khác.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "Tải lên hình ảnh",
     "upload_a_picture": "Tải lên một hình ảnh",
-    "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "remove_uploaded_picture": "Xoá ảnh đã tải lên",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "Thiết lập",
     "show_email": "Hiện Email của tôi",
     "show_fullname": "Hiện tên đầy đủ",
@@ -74,33 +76,34 @@
     "settings-require-reload": "Một số thay đổi trong cài đặt đòi hỏi tải lại. Nhấn vào đây để tải lại trang.",
     "has_no_follower": "Người dùng này hiện chưa có ai theo dõi :(",
     "follows_no_one": "Người dùng này hiện chưa theo dõi ai :(",
-    "has_no_posts": "This user hasn't posted anything yet.",
-    "has_no_topics": "This user hasn't posted any topics yet.",
-    "has_no_watched_topics": "This user hasn't watched any topics yet.",
-    "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.",
-    "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.",
-    "has_no_voted_posts": "This user has no voted posts",
+    "has_no_posts": "Thành viên này chưa đăng bài viết nào cả.",
+    "has_no_topics": "Thành viên này chưa đăng chủ đề nào cả.",
+    "has_no_watched_topics": "Thành viên này chưa theo dõi chủ đề nào cả.",
+    "has_no_upvoted_posts": "Thành viên này chưa tán thành bài viết nào cả.",
+    "has_no_downvoted_posts": "Thành viên này chưa phản đối bài viết nào cả.",
+    "has_no_voted_posts": "Thành viên này không có bài viết nào được tán thành.",
     "email_hidden": "Ẩn Email",
     "hidden": "Đã ẩn",
-    "paginate_description": "Paginate topics and posts instead of using infinite scroll",
+    "paginate_description": "Phân trang chủ đề và bài viết thay vì sử dụng cuộn vô hạn",
     "topics_per_page": "Số chủ đề trong một trang",
     "posts_per_page": "Số bài viết trong một trang",
-    "notification_sounds": "Play a sound when you receive a notification",
+    "notification_sounds": "Phát âm thanh khi bạn nhận được thông báo mới",
     "browsing": "Đang xem cài đặt",
-    "open_links_in_new_tab": "Open outgoing links in new tab",
+    "open_links_in_new_tab": "Mở link trong tab mới.",
     "enable_topic_searching": "Bật In-topic Searching",
-    "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
-    "follow_topics_you_reply_to": "Follow topics that you reply to",
-    "follow_topics_you_create": "Follow topics you create",
+    "topic_search_help": "Nếu bật, tìm kiếm trong chủ đề sẽ thay thế tính năng tính năng tìm kiếm của trình duyệt và cho phép bạn tìm kiếm trong toàn bộ chủ đề, thay vì chỉ tìm kiếm nội dung đang hiện thị trên màn hình",
+    "scroll_to_my_post": "After posting a reply, show the new post",
+    "follow_topics_you_reply_to": "Theo dõi chủ đề bạn trả lời",
+    "follow_topics_you_create": "Theo dõi chủ đề bạn tạo",
     "grouptitle": "Chọn tên nhóm mà bạn muốn hiển thị",
-    "no-group-title": "No group title",
-    "select-skin": "Select a Skin",
-    "select-homepage": "Select a Homepage",
-    "homepage": "Homepage",
-    "homepage_description": "Select a page to use as the forum homepage or 'None' to use the default homepage.",
-    "custom_route": "Custom Homepage Route",
-    "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")",
-    "sso.title": "Single Sign-on Services",
-    "sso.associated": "Associated with",
-    "sso.not-associated": "Click here to associate with"
+    "no-group-title": "Không có tên nhóm",
+    "select-skin": "Chọn một giao diện",
+    "select-homepage": "Chọn Trang chủ",
+    "homepage": "Trang chủ",
+    "homepage_description": "Chọn một trang để sử dụng như trang chủ của diễn đàn hoặc chọn \"None\" để sử dụng trang chủ mặc định.",
+    "custom_route": "Đường dẫn trang chủ tuỳ chọn",
+    "custom_route_help": "Nhập đường dẫn ở đây, mà không có dấu gạch chéo ở trước (chẳng hạn \"recent\" hoặc \"popular)",
+    "sso.title": "Đăng nhập một lần",
+    "sso.associated": "Đã liên kết với",
+    "sso.not-associated": "Nhấn vào đây để liên kết với"
 }
\ No newline at end of file
diff --git a/public/language/vi/users.json b/public/language/vi/users.json
index adf12ca433..0e60994360 100644
--- a/public/language/vi/users.json
+++ b/public/language/vi/users.json
@@ -1,20 +1,20 @@
 {
-    "latest_users": "Những người dùng mới nhất",
-    "top_posters": "Những người viết bài nhiều nhất",
-    "most_reputation": "Uy tín nhất",
+    "latest_users": "Thành viên mới nhất",
+    "top_posters": "Thành viên đăng bài nhiều nhất",
+    "most_reputation": "Thành viên có điểm tín nhiệm cao nhất",
     "search": "Tìm kiếm",
-    "enter_username": "Gõ tên người dùng để tìm kiếm",
+    "enter_username": "Gõ tên thành viên để tìm kiếm",
     "load_more": "Tải thêm",
-    "users-found-search-took": "%1 user(s) found! Search took %2 seconds.",
-    "filter-by": "Filter By",
-    "online-only": "Online only",
-    "invite": "Invite",
-    "invitation-email-sent": "An invitation email has been sent to %1",
-    "user_list": "User List",
-    "recent_topics": "Recent Topics",
-    "popular_topics": "Popular Topics",
-    "unread_topics": "Unread Topics",
-    "categories": "Categories",
-    "tags": "Tags",
-    "no-users-found": "No users found!"
+    "users-found-search-took": "Đã tìm thấy %1 thành viên! Tìm kiếm mất %2 giây.",
+    "filter-by": "Lọc bằng",
+    "online-only": "Đang online",
+    "invite": "Mời",
+    "invitation-email-sent": "Email mời đã được gửi tới %1",
+    "user_list": "Danh sách thành viên",
+    "recent_topics": "Chủ đề mới",
+    "popular_topics": "Chủ để nổi bật",
+    "unread_topics": "Chủ đề chưa đọc",
+    "categories": "Chuyên mục",
+    "tags": "Thẻ",
+    "no-users-found": "Không tìm thấy thành viên nào!"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json
index 730af9e35d..4e8ec6a25f 100644
--- a/public/language/zh_CN/error.json
+++ b/public/language/zh_CN/error.json
@@ -14,7 +14,7 @@
     "invalid-password": "无效密码",
     "invalid-username-or-password": "请确认用户名和密码",
     "invalid-search-term": "无效的搜索关键字",
-    "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
+    "invalid-pagination-value": "无效的分页数值,必须介于 %1 和 %2 之间",
     "username-taken": "此用户名已被占用",
     "email-taken": "此电子邮箱已被占用",
     "email-not-confirmed": "您的电子邮箱尚未确认,请点击这里确认您的电子邮箱。",
@@ -27,6 +27,7 @@
     "password-too-long": "密码太长",
     "user-banned": "用户已禁止",
     "user-too-new": "抱歉,您需要等待 %1 秒后,才可以发帖!",
+    "blacklisted-ip": "对不起,您的IP地址已被社区禁用。如果您认为这是一个错误,请与管理员联系。",
     "no-category": "版块不存在",
     "no-topic": "主题不存在",
     "no-post": "帖子不存在",
@@ -50,8 +51,8 @@
     "still-uploading": "请等待上传完成",
     "file-too-big": "上传文件的大小限制为 %1 KB - 请缩减文件大小",
     "guest-upload-disabled": "未登录用户不允许上传",
-    "already-favourited": "您已收藏该帖",
-    "already-unfavourited": "您已取消收藏此帖",
+    "already-favourited": "您已将此贴存为了书签。",
+    "already-unfavourited": "您已取消了此贴的书签",
     "cant-ban-other-admins": "您不能封禁其他管理员!",
     "cant-remove-last-admin": "您是唯一的管理员。在删除您的管理员权限前,请添加另一个管理员。",
     "invalid-image-type": "无效的图像类型。允许的类型有:%1",
@@ -83,7 +84,7 @@
     "chat-message-too-long": "聊天信息太长",
     "cant-edit-chat-message": "您不能编辑这条信息",
     "cant-remove-last-user": "您不能移除这个用户",
-    "cant-delete-chat-message": "You are not allowed to delete this message",
+    "cant-delete-chat-message": "您不允许删除这条消息",
     "reputation-system-disabled": "威望系统已禁用。",
     "downvoting-disabled": "扣分功能已禁用",
     "not-enough-reputation-to-downvote": "您的威望不足以给此帖扣分",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "请输入您的用户名登录",
     "invite-maximum-met": "您的邀请人数超出了上限 (%1 超过了 %2)。",
     "no-session-found": "未登录!",
-    "not-in-room": "User not in room"
+    "not-in-room": "用户已不在聊天室中",
+    "no-users-in-room": "这个聊天室中没有用户",
+    "cant-kick-self": "你不能把自己踢出群组"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json
index 0b11cde25a..d6a10e614c 100644
--- a/public/language/zh_CN/global.json
+++ b/public/language/zh_CN/global.json
@@ -50,8 +50,8 @@
     "topics": "主题",
     "posts": "帖子",
     "best": "最佳",
-    "upvoted": "Upvoted",
-    "downvoted": "Downvoted",
+    "upvoted": "顶",
+    "downvoted": "踩",
     "views": "浏览",
     "reputation": "威望",
     "read_more": "阅读更多",
@@ -65,7 +65,7 @@
     "posted_in_ago_by": "%3 于 %1 发布到 %2",
     "user_posted_ago": "%1 发布于 %2",
     "guest_posted_ago": "游客发布于 %1",
-    "last_edited_by": "last edited by %1",
+    "last_edited_by": "最后由 %1 编辑",
     "norecentposts": "暂无新帖",
     "norecenttopics": "暂无新主题",
     "recentposts": "新帖",
@@ -86,5 +86,9 @@
     "delete_all": "全部删除",
     "map": "地图",
     "sessions": "已登录的会话",
-    "ip_address": "IP地址"
+    "ip_address": "IP地址",
+    "enter_page_number": "输入页码",
+    "upload_file": "上传文件",
+    "upload": "上传",
+    "allowed-file-types": "允许的文件类型有 %1"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/groups.json b/public/language/zh_CN/groups.json
index 81e38901cb..f988de4db6 100644
--- a/public/language/zh_CN/groups.json
+++ b/public/language/zh_CN/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "隐藏",
     "details.hidden_help": "启用此选项后,小组将不在小组列表中展现,成员只能通过邀请加入。",
     "details.delete_group": "删除小组",
+    "details.private_system_help": "系统禁用了私有群组,这个选项不起任何作用",
     "event.updated": "小组信息已更新",
     "event.deleted": "小组 \"%1\" 已被删除",
     "membership.accept-invitation": "接受邀请",
@@ -48,5 +49,6 @@
     "membership.join-group": "加入小组",
     "membership.leave-group": "退出小组",
     "membership.reject": "拒绝",
-    "new-group.group_name": "组名: "
+    "new-group.group_name": "组名: ",
+    "upload-group-cover": "上传组封面"
 }
\ No newline at end of file
diff --git a/public/language/zh_CN/modules.json b/public/language/zh_CN/modules.json
index 4dd7bd27d2..bf50d3f496 100644
--- a/public/language/zh_CN/modules.json
+++ b/public/language/zh_CN/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 正在输入……",
     "chat.user_has_messaged_you": "%1 向您发送了消息。",
     "chat.see_all": "查看所有对话",
+    "chat.mark_all_read": "将所有聊天标为已读",
     "chat.no-messages": "请选择接收人,以查看聊天消息历史",
     "chat.no-users-in-room": "此聊天室中没有用户",
     "chat.recent-chats": "最近聊天",
diff --git a/public/language/zh_CN/notifications.json b/public/language/zh_CN/notifications.json
index 2880311394..6d45be4d19 100644
--- a/public/language/zh_CN/notifications.json
+++ b/public/language/zh_CN/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> 和 %2 个其他人在 <strong>%3</strong> 赞了您的帖子。",
     "moved_your_post": "您的帖子已被 <strong>%1</strong> 移动到了 <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> 移动到了 <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> 在 <strong>%2</strong> 收藏了您的帖子。",
-    "favourited_your_post_in_dual": "<strong>%1</strong> 和 <strong>%2</strong> 在 <strong>%3</strong> 收藏了您的帖子。",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> 和 %2 个其他人在 <strong>%3</strong> 收藏了您的帖子。",
+    "favourited_your_post_in": "<strong>%1</strong> 将您在 <strong>%2</strong> 的帖子添加到了书签",
+    "favourited_your_post_in_dual": "<strong>%1</strong> 和 <strong>%2</strong> 将您在 <strong>%3</strong> 的帖子添加到了书签",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> 和其他 %2 人 将您在 <strong>%2</strong> 的帖子添加到了书签",
     "user_flagged_post_in": "<strong>%1</strong> 在 <strong>%2</strong> 标记了一个帖子",
     "user_flagged_post_in_dual": "<strong>%1</strong> 和 <strong>%2</strong> 在 <strong>%3</strong> 标记了一个帖子",
     "user_flagged_post_in_multiple": "<strong>%1</strong> 和 %2 个其他人在 <strong>%3</strong> 标记了一个帖子",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> 和 <strong>%2</strong> 关注了您。",
     "user_started_following_you_multiple": "<strong>%1</strong> 和 %2 个其他人关注了您。",
     "new_register": "<strong>%1</strong> 发出了注册请求",
+    "new_register_multiple": "有 <strong>%1</strong> 条注册申请等待批准。",
     "email-confirmed": "电子邮箱已确认",
     "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已全面激活。",
     "email-confirm-error-message": "验证您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。",
diff --git a/public/language/zh_CN/pages.json b/public/language/zh_CN/pages.json
index 1b8ffa7227..728ed037b8 100644
--- a/public/language/zh_CN/pages.json
+++ b/public/language/zh_CN/pages.json
@@ -6,12 +6,12 @@
     "popular-month": "当月热门话题",
     "popular-alltime": "热门主题",
     "recent": "最新主题",
-    "flagged-posts": "Flagged Posts",
+    "flagged-posts": "已举报的帖子",
     "users/online": "在线会员",
     "users/latest": "最新会员",
     "users/sort-posts": "最多发帖的会员",
     "users/sort-reputation": "最多积分的会员",
-    "users/banned": "Banned Users",
+    "users/banned": "被封禁的用户",
     "users/search": "会员搜索",
     "notifications": "提醒",
     "tags": "话题",
@@ -39,6 +39,7 @@
     "account/upvoted": "帖子被 %1 顶过",
     "account/downvoted": "帖子被 %1 踩过",
     "account/best": "%1 发布的最佳帖子",
+    "confirm": "电子邮箱已确认",
     "maintenance.text": "%1 正在进行维护。请稍后再来。",
     "maintenance.messageIntro": "此外,管理员留下的消息:",
     "throttled.text": "%1 因负荷超载暂不可用。请稍后再来。"
diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json
index eb76dc0eaf..c123366bb0 100644
--- a/public/language/zh_CN/topic.json
+++ b/public/language/zh_CN/topic.json
@@ -35,7 +35,7 @@
     "login_to_subscribe": "请注册或登录后,再订阅此主题。",
     "markAsUnreadForAll.success": "将全部主题标为未读。",
     "mark_unread": "标记为未读",
-    "mark_unread.success": "Topic marked as unread.",
+    "mark_unread.success": "未读话题",
     "watch": "关注",
     "unwatch": "取消关注",
     "watch.title": "当此主题有新回复时,通知我",
@@ -65,9 +65,9 @@
     "disabled_categories_note": "停用的版面为灰色",
     "confirm_move": "移动",
     "confirm_fork": "分割",
-    "favourite": "收藏",
-    "favourites": "收藏",
-    "favourites.has_no_favourites": "您还没有任何收藏,收藏后的帖子会显示在这里!",
+    "favourite": "书签",
+    "favourites": "书签",
+    "favourites.has_no_favourites": "您还没有添加任何书签",
     "loading_more_posts": "正在加载更多帖子",
     "move_topic": "移动主题",
     "move_topics": "移动主题",
@@ -105,7 +105,7 @@
     "stale.warning": "您回复的主题已经非常老了。开个新帖,然后在新帖中引用这个老帖的内容,可以吗?",
     "stale.create": "创建新主题",
     "stale.reply_anyway": "仍然回复此帖",
-    "link_back": "Re: [%1](%2)",
+    "link_back": "回复: [%1](%2)",
     "spam": "垃圾帖",
     "offensive": "人身攻击",
     "custom-flag-reason": "输入举报原因"
diff --git a/public/language/zh_CN/uploads.json b/public/language/zh_CN/uploads.json
new file mode 100644
index 0000000000..9c86a89605
--- /dev/null
+++ b/public/language/zh_CN/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "正在上传文件...",
+    "select-file-to-upload": "请选择需要上传的文件!",
+    "upload-success": "文件上传成功!",
+    "maximum-file-size": "最大 %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/zh_CN/user.json b/public/language/zh_CN/user.json
index fd4439ca6b..e713189b25 100644
--- a/public/language/zh_CN/user.json
+++ b/public/language/zh_CN/user.json
@@ -22,7 +22,7 @@
     "profile": "资料",
     "profile_views": "资料浏览",
     "reputation": "威望",
-    "favourites": "收藏的帖子",
+    "favourites": "书签",
     "watched": "已订阅",
     "followers": "粉丝",
     "following": "关注",
@@ -39,6 +39,7 @@
     "change_username": "更改用户名",
     "change_email": "更改电子邮箱",
     "edit": "编辑",
+    "edit-profile": "编辑资料",
     "default_picture": "缺省图标",
     "uploaded_picture": "已有头像",
     "upload_new_picture": "上传新头像",
@@ -55,10 +56,11 @@
     "password": "密码",
     "username_taken_workaround": "您申请的用户名已被占用,所以我们稍作更改。您现在的用户名是 <strong>%1</strong>",
     "password_same_as_username": "您的密码与用户名相同,请选择另外的密码。",
+    "password_same_as_email": "您的密码与邮箱相同,请选择另外的密码。",
     "upload_picture": "上传头像",
     "upload_a_picture": "上传头像",
     "remove_uploaded_picture": "删除已上传的头像",
-    "image_spec": "您只可以上传PNG、JPG或 BMP 格式的文件",
+    "upload_cover_picture": "上传个人资料封面图片",
     "settings": "设置",
     "show_email": "显示我的电子邮箱",
     "show_fullname": "显示我的全名",
@@ -77,9 +79,9 @@
     "has_no_posts": "此用户从未发言。",
     "has_no_topics": "此用户还未发布任何主题。",
     "has_no_watched_topics": "此用户还未关注任何主题。",
-    "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.",
-    "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.",
-    "has_no_voted_posts": "This user has no voted posts",
+    "has_no_upvoted_posts": "此用户还未顶过任何帖子。",
+    "has_no_downvoted_posts": "此用户还未踩过任何帖子。",
+    "has_no_voted_posts": "这个用户还未评价任何帖子",
     "email_hidden": "电子邮箱已隐藏",
     "hidden": "隐藏",
     "paginate_description": "使用分页式版块浏览",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "在新标签打开外部链接",
     "enable_topic_searching": "启用主题内搜索",
     "topic_search_help": "如果启用此项,主题内搜索会替代浏览器默认的页面搜索,您将可以在整个主题内搜索,而不仅仅只搜索页面上展现的内容。",
+    "scroll_to_my_post": "在提交回复之后显示新帖子",
     "follow_topics_you_reply_to": "关注您回复的主题",
     "follow_topics_you_create": "关注您创建的主题",
     "grouptitle": "选择展示的小组称号",
diff --git a/public/language/zh_CN/users.json b/public/language/zh_CN/users.json
index 51e8d6d3d9..50d6dc0c23 100644
--- a/public/language/zh_CN/users.json
+++ b/public/language/zh_CN/users.json
@@ -16,5 +16,5 @@
     "unread_topics": "未读主题",
     "categories": "版面",
     "tags": "话题",
-    "no-users-found": "No users found!"
+    "no-users-found": "未找到匹配的用户!"
 }
\ No newline at end of file
diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json
index c36a4ed0bb..0db5a671c2 100644
--- a/public/language/zh_TW/error.json
+++ b/public/language/zh_TW/error.json
@@ -27,6 +27,7 @@
     "password-too-long": "Password too long",
     "user-banned": "該使用者已被停用",
     "user-too-new": "抱歉,發表您第一篇文章須要等待 %1 秒",
+    "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
     "no-category": "類別並不存在",
     "no-topic": "主題並不存在",
     "no-post": "文章並不存在",
@@ -50,8 +51,8 @@
     "still-uploading": "請等待上傳完成。",
     "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
     "guest-upload-disabled": "Guest uploading has been disabled",
-    "already-favourited": "你已經收藏了這篇文章",
-    "already-unfavourited": "你已放棄收藏這篇文章",
+    "already-favourited": "You have already bookmarked this post",
+    "already-unfavourited": "You have already unbookmarked this post",
     "cant-ban-other-admins": "您無法禁止其他的管理員!",
     "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
     "invalid-image-type": "無效的圖像類型。允許的類型:%1",
@@ -96,5 +97,7 @@
     "wrong-login-type-username": "請使用您的使用者名稱進行登錄",
     "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).",
     "no-session-found": "No login session found!",
-    "not-in-room": "User not in room"
+    "not-in-room": "User not in room",
+    "no-users-in-room": "No users in this room",
+    "cant-kick-self": "You can't kick yourself from the group"
 }
\ No newline at end of file
diff --git a/public/language/zh_TW/global.json b/public/language/zh_TW/global.json
index 82d1ff0589..10e4169e07 100644
--- a/public/language/zh_TW/global.json
+++ b/public/language/zh_TW/global.json
@@ -86,5 +86,9 @@
     "delete_all": "全部刪除",
     "map": "Map",
     "sessions": "Login Sessions",
-    "ip_address": "IP Address"
+    "ip_address": "IP Address",
+    "enter_page_number": "Enter page number",
+    "upload_file": "Upload file",
+    "upload": "Upload",
+    "allowed-file-types": "Allowed file types are %1"
 }
\ No newline at end of file
diff --git a/public/language/zh_TW/groups.json b/public/language/zh_TW/groups.json
index 170bf7ee23..d562fb5ccd 100644
--- a/public/language/zh_TW/groups.json
+++ b/public/language/zh_TW/groups.json
@@ -41,6 +41,7 @@
     "details.hidden": "隱藏",
     "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
     "details.delete_group": "Delete Group",
+    "details.private_system_help": "Private groups is disabled at system level, this option does not do anything",
     "event.updated": "群組詳細訊息已被更新",
     "event.deleted": "此 \"%1\" 群組已被刪除了",
     "membership.accept-invitation": "Accept Invitation",
@@ -48,5 +49,6 @@
     "membership.join-group": "Join Group",
     "membership.leave-group": "Leave Group",
     "membership.reject": "Reject",
-    "new-group.group_name": "Group Name:"
+    "new-group.group_name": "Group Name:",
+    "upload-group-cover": "Upload group cover"
 }
\ No newline at end of file
diff --git a/public/language/zh_TW/modules.json b/public/language/zh_TW/modules.json
index 733947b41a..c34132769a 100644
--- a/public/language/zh_TW/modules.json
+++ b/public/language/zh_TW/modules.json
@@ -6,6 +6,7 @@
     "chat.user_typing": "%1 正在輸入中...",
     "chat.user_has_messaged_you": "%1 已傳送訊息給你了",
     "chat.see_all": "See all chats",
+    "chat.mark_all_read": "Mark all chats read",
     "chat.no-messages": "請選擇收件人來查看聊天記錄",
     "chat.no-users-in-room": "No users in this room",
     "chat.recent-chats": "最近的聊天記錄",
diff --git a/public/language/zh_TW/notifications.json b/public/language/zh_TW/notifications.json
index 4711f56a36..3174feb38e 100644
--- a/public/language/zh_TW/notifications.json
+++ b/public/language/zh_TW/notifications.json
@@ -16,9 +16,9 @@
     "upvoted_your_post_in_multiple": "<strong>%1</strong> and %2 others have upvoted your post in <strong>%3</strong>.",
     "moved_your_post": "<strong>%1</strong> has moved your post to <strong>%2</strong>",
     "moved_your_topic": "<strong>%1</strong> has moved <strong>%2</strong>",
-    "favourited_your_post_in": "<strong>%1</strong> 收藏了你在 <strong>%2</strong>的post。",
-    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have favourited your post in <strong>%3</strong>.",
-    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have favourited your post in <strong>%3</strong>.",
+    "favourited_your_post_in": "<strong>%1</strong> has bookmarked your post in <strong>%2</strong>.",
+    "favourited_your_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> have bookmarked your post in <strong>%3</strong>.",
+    "favourited_your_post_in_multiple": "<strong>%1</strong> and %2 others have bookmarked your post in <strong>%3</strong>.",
     "user_flagged_post_in": "<strong>%1</strong> 舉報了 <strong>%2</strong>裡的一個post。",
     "user_flagged_post_in_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a post in <strong>%3</strong>",
     "user_flagged_post_in_multiple": "<strong>%1</strong> and %2 others flagged a post in <strong>%3</strong>",
@@ -30,6 +30,7 @@
     "user_started_following_you_dual": "<strong>%1</strong> and <strong>%2</strong> started following you.",
     "user_started_following_you_multiple": "<strong>%1</strong> and %2 others started following you.",
     "new_register": "<strong>%1</strong> sent a registration request.",
+    "new_register_multiple": "There are <strong>%1</strong> registration requests awaiting review.",
     "email-confirmed": "已確認電郵",
     "email-confirmed-message": "感謝您驗證您的電郵。您的帳戶現已全面啟用。",
     "email-confirm-error-message": "驗證您的電郵地址時出現問題。也許啟動碼無效或已過期。",
diff --git a/public/language/zh_TW/pages.json b/public/language/zh_TW/pages.json
index be4eda4d90..121be06593 100644
--- a/public/language/zh_TW/pages.json
+++ b/public/language/zh_TW/pages.json
@@ -33,12 +33,13 @@
     "account/posts": "Posts made by %1",
     "account/topics": "Topics created by %1",
     "account/groups": "%1's Groups",
-    "account/favourites": "%1's Favourite Posts",
+    "account/favourites": "%1's Bookmarked Posts",
     "account/settings": "User Settings",
     "account/watched": "Topics watched by %1",
     "account/upvoted": "Posts upvoted by %1",
     "account/downvoted": "Posts downvoted by %1",
     "account/best": "Best posts made by %1",
+    "confirm": "Email Confirmed",
     "maintenance.text": "目前 %1 正在進行維修。請稍後再來。",
     "maintenance.messageIntro": "此外,管理員有以下訊息:",
     "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time."
diff --git a/public/language/zh_TW/topic.json b/public/language/zh_TW/topic.json
index 39184aa828..62f48e9523 100644
--- a/public/language/zh_TW/topic.json
+++ b/public/language/zh_TW/topic.json
@@ -65,9 +65,9 @@
     "disabled_categories_note": "停用的版面為灰色",
     "confirm_move": "移動",
     "confirm_fork": "作為主題",
-    "favourite": "收藏",
-    "favourites": "收藏",
-    "favourites.has_no_favourites": "你還沒有任何收藏,收藏的文章將會出現在這裡!",
+    "favourite": "Bookmark",
+    "favourites": "Bookmarks",
+    "favourites.has_no_favourites": "You haven't bookmarked any posts yet.",
     "loading_more_posts": "載入更多文章",
     "move_topic": "移動主題",
     "move_topics": "移動主題",
diff --git a/public/language/zh_TW/uploads.json b/public/language/zh_TW/uploads.json
new file mode 100644
index 0000000000..1622cb5693
--- /dev/null
+++ b/public/language/zh_TW/uploads.json
@@ -0,0 +1,6 @@
+{
+    "uploading-file": "Uploading the file...",
+    "select-file-to-upload": "Select a file to upload!",
+    "upload-success": "File uploaded successfully!",
+    "maximum-file-size": "Maximum %1 kb"
+}
\ No newline at end of file
diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json
index e0c737d761..118ba2f471 100644
--- a/public/language/zh_TW/user.json
+++ b/public/language/zh_TW/user.json
@@ -22,7 +22,7 @@
     "profile": "個人資料",
     "profile_views": "資料被查看",
     "reputation": "聲譽",
-    "favourites": "我的最愛",
+    "favourites": "Bookmarks",
     "watched": "觀看者",
     "followers": "跟隨者",
     "following": "正在關注",
@@ -39,6 +39,7 @@
     "change_username": "Change Username",
     "change_email": "Change Email",
     "edit": "編輯",
+    "edit-profile": "Edit Profile",
     "default_picture": "Default Icon",
     "uploaded_picture": "已有頭像",
     "upload_new_picture": "上傳新頭像",
@@ -55,10 +56,11 @@
     "password": "密碼",
     "username_taken_workaround": "您所註冊的使用者名稱已經被使用了,所以我們將它略微改變。你現在改稱 <strong>%1</strong>",
     "password_same_as_username": "Your password is the same as your username, please select another password.",
+    "password_same_as_email": "Your password is the same as your email, please select another password.",
     "upload_picture": "上傳頭像",
     "upload_a_picture": "上傳一張照片",
     "remove_uploaded_picture": "Remove Uploaded Picture",
-    "image_spec": "You may only upload PNG, JPG, or BMP files",
+    "upload_cover_picture": "Upload cover picture",
     "settings": "設定",
     "show_email": "顯示我的郵箱",
     "show_fullname": "顯示我的全名",
@@ -90,6 +92,7 @@
     "open_links_in_new_tab": "Open outgoing links in new tab",
     "enable_topic_searching": "Enable In-Topic Searching",
     "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
+    "scroll_to_my_post": "After posting a reply, show the new post",
     "follow_topics_you_reply_to": "Follow topics that you reply to",
     "follow_topics_you_create": "Follow topics you create",
     "grouptitle": "Select the group title you would like to display",
diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less
index 930e518412..e733d8d9bd 100644
--- a/public/less/admin/admin.less
+++ b/public/less/admin/admin.less
@@ -21,6 +21,7 @@
 @import "./settings";
 
 @import "../flags";
+@import "../blacklist";
 
 @import "./modules/alerts";
 @import "./modules/selectable";
@@ -238,5 +239,5 @@
 }
 
 [class^="col-"] .mdl-switch__label {
-	padding-right: 15px;	
+	padding-right: 15px;
 }
\ No newline at end of file
diff --git a/public/less/blacklist.less b/public/less/blacklist.less
new file mode 100644
index 0000000000..5f23cd5698
--- /dev/null
+++ b/public/less/blacklist.less
@@ -0,0 +1,6 @@
+#blacklist-rules {
+	width: 100%;
+	height: 450px;
+	display: block;
+	border: 1px solid #eee;
+}
\ No newline at end of file
diff --git a/public/less/generics.less b/public/less/generics.less
index 5337609b4f..fee1157662 100644
--- a/public/less/generics.less
+++ b/public/less/generics.less
@@ -1,3 +1,20 @@
+.define-if-not-set() {
+	@gray-base:              #000;
+	@gray-darker:            lighten(@gray-base, 13.5%); // #222
+	@gray-dark:              lighten(@gray-base, 20%);   // #333
+	@gray:                   lighten(@gray-base, 33.5%); // #555
+	@gray-light:             lighten(@gray-base, 46.7%); // #777
+	@gray-lighter:           lighten(@gray-base, 93.5%); // #eee
+
+	@brand-primary:         darken(#428bca, 6.5%); // #337ab7
+	@brand-success:         #5cb85c;
+	@brand-info:            #5bc0de;
+	@brand-warning:         #f0ad4e;
+	@brand-danger:          #d9534f;
+}
+
+.define-if-not-set();
+
 .category-list {
 	padding: 0;
 
diff --git a/public/less/global.less b/public/less/global.less
new file mode 100644
index 0000000000..d606b8221c
--- /dev/null
+++ b/public/less/global.less
@@ -0,0 +1,26 @@
+/*
+	This stylesheet is applied to all themes and all pages.
+	They can be overridden by themes, though their presence (or initial settings) may be depended upon by
+	client-side logic in core.
+
+	==========
+*/
+
+/* Prevent viewport shuffling on image load by restricting image dimensions until viewed by the browser */
+[component="post/content"] img {
+	transition: width 500ms ease;
+	transition: height 500ms ease;
+	transition: opacity 500ms ease;
+
+	&[data-state="unloaded"], &[data-state="loading"] {
+		display: inherit;
+		height: 0;
+		opacity: 0;
+	}
+
+	&[data-state="loaded"] {
+		display: inherit;
+		height: auto;
+		opacity: 1;
+	}
+}
\ No newline at end of file
diff --git a/public/less/mixins.less b/public/less/mixins.less
index 4bee3f27bd..57a54ea32b 100644
--- a/public/less/mixins.less
+++ b/public/less/mixins.less
@@ -79,4 +79,9 @@
 	height: @size;
 	line-height: @size;
 	font-size: @font-size;
+}
+
+.box-shadow(@shadow) {
+	-webkit-box-shadow: @shadow;
+	box-shadow: @shadow;
 }
\ No newline at end of file
diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js
index 173148ef5e..d5a5e1f8d3 100644
--- a/public/src/admin/admin.js
+++ b/public/src/admin/admin.js
@@ -16,21 +16,21 @@
 			});
 		}
 
-		$(window).on('action:ajaxify.contentLoaded', function(ev, data) {
-			var url = data.url;
-
-			selectMenuItem(data.url);
-			setupRestartLinks();
-
-			componentHandler.upgradeDom();
-		});
-
 		$('[component="logout"]').on('click', app.logout);
 		app.alert = launchSnackbar;
 
 		configureSlidemenu();
 	});
 
+	$(window).on('action:ajaxify.contentLoaded', function(ev, data) {
+		var url = data.url;
+
+		selectMenuItem(data.url);
+		setupRestartLinks();
+
+		componentHandler.upgradeDom();
+	});
+
 	function setupKeybindings() {
 		require(['mousetrap'], function(mousetrap) {
 			mousetrap.bind('ctrl+shift+a r', function() {
diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js
index 6062cd8695..9a130723bf 100644
--- a/public/src/admin/extend/plugins.js
+++ b/public/src/admin/extend/plugins.js
@@ -230,7 +230,7 @@ define('admin/extend/plugins', function() {
 	function populateUpgradeablePlugins() {
 		$('#installed ul li').each(function() {
 			if ($(this).children('[data-action="upgrade"]').length) {
-				$('#upgrade ul').append($(this));
+				$('#upgrade ul').append($(this).clone(true));
 			}
 		});
 	}
diff --git a/public/src/admin/general/dashboard.js b/public/src/admin/general/dashboard.js
index b23f57e3c8..a26ed9ad5e 100644
--- a/public/src/admin/general/dashboard.js
+++ b/public/src/admin/general/dashboard.js
@@ -8,6 +8,7 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) {
 			graphs: false
 		},
 		isMobile = false,
+		isPrerelease = /^v?\d+\.\d+\.\d+-.+$/,
 		graphData = {
 			rooms: {},
 			traffic: {}
@@ -22,7 +23,17 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) {
 		graphInterval: 15000,
 		realtimeInterval: 1500
 	};
+	
+	$(window).on('action:ajaxify.start', function(ev, data) {
+		clearInterval(intervals.rooms);
+		clearInterval(intervals.graphs);
 
+		intervals.rooms = null;
+		intervals.graphs = null;
+		graphData.rooms = null;
+		graphData.traffic = null;
+		usedTopicColors.length = 0;
+	});
 
 	Admin.init = function() {
 		app.enterRoom('admin');
@@ -30,37 +41,34 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) {
 
 		isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
 
-		$(window).on('action:ajaxify.start', function(ev, data) {
-			clearInterval(intervals.rooms);
-			clearInterval(intervals.graphs);
-
-			intervals.rooms = null;
-			intervals.graphs = null;
-			graphData.rooms = null;
-			graphData.traffic = null;
-			usedTopicColors.length = 0;
-		});
-
 		$.get('https://api.github.com/repos/NodeBB/NodeBB/tags', function(releases) {
 			// Re-sort the releases, as they do not follow Semver (wrt pre-releases)
 			releases = releases.sort(function(a, b) {
 				a = a.name.replace(/^v/, '');
 				b = b.name.replace(/^v/, '');
 				return semver.lt(a, b) ? 1 : -1;
+			}).filter(function(version) {
+				return !isPrerelease.test(version.name);	// filter out automated prerelease versions
 			});
 
 			var	version = $('#version').html(),
 				latestVersion = releases[0].name.slice(1),
 				checkEl = $('.version-check');
-			checkEl.html($('.version-check').html().replace('<i class="fa fa-spinner fa-spin"></i>', 'v' + latestVersion));
 
 			// Alter box colour accordingly
 			if (semver.eq(latestVersion, version)) {
 				checkEl.removeClass('alert-info').addClass('alert-success');
 				checkEl.append('<p>You are <strong>up-to-date</strong> <i class="fa fa-check"></i></p>');
 			} else if (semver.gt(latestVersion, version)) {
-				checkEl.removeClass('alert-info').addClass('alert-danger');
-				checkEl.append('<p>A new version (v' + latestVersion + ') has been released. Consider <a href="https://docs.nodebb.org/en/latest/upgrading/index.html">upgrading your NodeBB</a>.</p>');
+				checkEl.removeClass('alert-info').addClass('alert-warning');
+				if (!isPrerelease.test(version)) {
+					checkEl.append('<p>A new version (v' + latestVersion + ') has been released. Consider <a href="https://docs.nodebb.org/en/latest/upgrading/index.html">upgrading your NodeBB</a>.</p>');
+				} else {
+					checkEl.append('<p>This is an outdated pre-release version of NodeBB. A new version (v' + latestVersion + ') has been released. Consider <a href="https://docs.nodebb.org/en/latest/upgrading/index.html">upgrading your NodeBB</a>.</p>');
+				}
+			} else if (isPrerelease.test(version)) {
+				checkEl.removeClass('alert-info').addClass('alert-info');
+				checkEl.append('<p>This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class="fa fa-exclamation-triangle"></i>.</p>');
 			}
 		});
 
@@ -158,6 +166,7 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) {
 
 		if (isMobile) {
 			Chart.defaults.global.showTooltips = false;
+			Chart.defaults.global.animation = false;
 		}
 
 		var data = {
@@ -186,7 +195,7 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) {
 				]
 			};
 
-		trafficCanvas.width = $(trafficCanvas).parent().width(); // is this necessary
+		trafficCanvas.width = $(trafficCanvas).parent().width();
 		graphs.traffic = new Chart(trafficCtx).Line(data, {
 			responsive: true
 		});
diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js
index a57fc06701..b4a5f19601 100644
--- a/public/src/admin/manage/categories.js
+++ b/public/src/admin/manage/categories.js
@@ -152,28 +152,45 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
 	 * @param parentId {number} parent category identifier
 	 */
 	function renderList(categories, container, parentId){
-		templates.parse('admin/partials/categories/category-rows', {
-			cid: parentId,
-			categories: categories
-		}, function(html) {
-			container.append(html);
-
-			// Handle and children categories in this level have
-			for(var x=0,numCategories=categories.length;x<numCategories;x++) {
-				renderList(categories[x].children, $('li[data-cid="' + categories[x].cid + '"]'), categories[x].cid);
-			}
+		// Translate category names if needed
+		var count = 0;
+		categories.forEach(function(category, idx, parent) {
+			translator.translate(category.name, function(translated) {
+				if (category.name !== translated) {
+					category.name = translated;
+				}
+				++count;
 
-			// Make list sortable
-			sortables[parentId] = Sortable.create($('ul[data-cid="' + parentId + '"]')[0], {
-				group: 'cross-categories',
-				animation: 150,
-				handle: '.icon',
-				dataIdAttr: 'data-cid',
-				ghostClass: "placeholder",
-				onAdd: itemDidAdd,
-				onEnd: itemDragDidEnd
+				if (count === parent.length) {
+					continueRender();
+				}
 			});
 		});
+
+		function continueRender() {
+			templates.parse('admin/partials/categories/category-rows', {
+				cid: parentId,
+				categories: categories
+			}, function(html) {
+				container.append(html);
+
+				// Handle and children categories in this level have
+				for(var x=0,numCategories=categories.length;x<numCategories;x++) {
+					renderList(categories[x].children, $('li[data-cid="' + categories[x].cid + '"]'), categories[x].cid);
+				}
+
+				// Make list sortable
+				sortables[parentId] = Sortable.create($('ul[data-cid="' + parentId + '"]')[0], {
+					group: 'cross-categories',
+					animation: 150,
+					handle: '.icon',
+					dataIdAttr: 'data-cid',
+					ghostClass: "placeholder",
+					onAdd: itemDidAdd,
+					onEnd: itemDragDidEnd
+				});
+			});
+		}
 	}
 
 	return Categories;
diff --git a/public/src/admin/manage/category-analytics.js b/public/src/admin/manage/category-analytics.js
new file mode 100644
index 0000000000..52a24ecfeb
--- /dev/null
+++ b/public/src/admin/manage/category-analytics.js
@@ -0,0 +1,109 @@
+"use strict";
+/*global config, define, app, socket, ajaxify, bootbox, templates, Chart, utils */
+
+define('admin/manage/category-analytics', [], function() {
+	var CategoryAnalytics = {};
+
+	CategoryAnalytics.init = function() {
+		var hourlyCanvas = document.getElementById('pageviews:hourly'),
+			dailyCanvas = document.getElementById('pageviews:daily'),
+			topicsCanvas = document.getElementById('topics:daily'),
+			postsCanvas = document.getElementById('posts:daily'),
+			hourlyLabels = utils.getHoursArray().map(function(text, idx) {
+				return idx % 3 ? '' : text;
+			}),
+			dailyLabels = utils.getDaysArray().map(function(text, idx) {
+				return idx % 3 ? '' : text;
+			});
+
+		if (utils.isMobile()) {
+			Chart.defaults.global.showTooltips = false;
+		}
+
+		var data = {
+			'pageviews:hourly': {
+				labels: hourlyLabels,
+				datasets: [
+					{
+						label: "",
+						fillColor: "rgba(186,139,175,0.2)",
+						strokeColor: "rgba(186,139,175,1)",
+						pointColor: "rgba(186,139,175,1)",
+						pointStrokeColor: "#fff",
+						pointHighlightFill: "#fff",
+						pointHighlightStroke: "rgba(186,139,175,1)",
+						data: ajaxify.data.analytics['pageviews:hourly']
+					}
+				]
+			},
+			'pageviews:daily': {
+				labels: dailyLabels,
+				datasets: [
+					{
+						label: "",
+						fillColor: "rgba(151,187,205,0.2)",
+						strokeColor: "rgba(151,187,205,1)",
+						pointColor: "rgba(151,187,205,1)",
+						pointStrokeColor: "#fff",
+						pointHighlightFill: "#fff",
+						pointHighlightStroke: "rgba(151,187,205,1)",
+						data: ajaxify.data.analytics['pageviews:daily']
+					}
+				]
+			},
+			'topics:daily': {
+				labels: dailyLabels.slice(-7),
+				datasets: [
+					{
+						label: "",
+						fillColor: "rgba(171,70,66,0.2)",
+						strokeColor: "rgba(171,70,66,1)",
+						pointColor: "rgba(171,70,66,1)",
+						pointStrokeColor: "#fff",
+						pointHighlightFill: "#fff",
+						pointHighlightStroke: "rgba(171,70,66,1)",
+						data: ajaxify.data.analytics['topics:daily']
+					}
+				]
+			},
+			'posts:daily': {
+				labels: dailyLabels.slice(-7),
+				datasets: [
+					{
+						label: "",
+						fillColor: "rgba(161,181,108,0.2)",
+						strokeColor: "rgba(161,181,108,1)",
+						pointColor: "rgba(161,181,108,1)",
+						pointStrokeColor: "#fff",
+						pointHighlightFill: "#fff",
+						pointHighlightStroke: "rgba(161,181,108,1)",
+						data: ajaxify.data.analytics['posts:daily']
+					}
+				]
+			},
+		};
+
+		hourlyCanvas.width = $(hourlyCanvas).parent().width();
+		dailyCanvas.width = $(dailyCanvas).parent().width();
+		topicsCanvas.width = $(topicsCanvas).parent().width();
+		postsCanvas.width = $(postsCanvas).parent().width();
+		new Chart(hourlyCanvas.getContext('2d')).Line(data['pageviews:hourly'], {
+			responsive: true,
+			animation: false
+		});
+		new Chart(dailyCanvas.getContext('2d')).Line(data['pageviews:daily'], {
+			responsive: true,
+			animation: false
+		});
+		new Chart(topicsCanvas.getContext('2d')).Line(data['topics:daily'], {
+			responsive: true,
+			animation: false
+		});
+		new Chart(postsCanvas.getContext('2d')).Line(data['posts:daily'], {
+			responsive: true,
+			animation: false
+		});
+	};
+
+	return CategoryAnalytics;
+});
\ No newline at end of file
diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js
index 32a4c99e9b..ced83105a3 100644
--- a/public/src/admin/manage/category.js
+++ b/public/src/admin/manage/category.js
@@ -1,5 +1,5 @@
 "use strict";
-/*global define, app, socket, ajaxify, RELATIVE_PATH, bootbox, templates */
+/*global config, define, app, socket, ajaxify, bootbox, templates */
 
 define('admin/manage/category', [
 	'uploader',
@@ -76,7 +76,7 @@ define('admin/manage/category', [
 				}
 			});
 
-		$('[data-name="imageClass"]').on('change', function(ev) {
+		$('[data-name="imageClass"]').on('change', function() {
 			$('.category-preview').css('background-size', $(this).val());
 		});
 
@@ -100,23 +100,74 @@ define('admin/manage/category', [
 			});
 		});
 
-		$('.upload-button').on('click', function() {
-			var inputEl = $(this),
-				cid = inputEl.attr('data-cid');
+		$('.copy-settings').on('click', function(e) {
+			e.preventDefault();
+			socket.emit('admin.categories.getNames', function(err, categories) {
+				if (err) {
+					return app.alertError(err.message);
+				}
+
+				templates.parse('admin/partials/categories/select-category', {
+					categories: categories
+				}, function(html) {
+					function submit() {
+						var formData = modal.find('form').serializeObject();
+
+						socket.emit('admin.categories.copySettingsFrom', {fromCid: formData['select-cid'], toCid: ajaxify.data.category.cid}, function(err) {
+							if (err) {
+								return app.alertError(err.message);
+							}
+							app.alertSuccess('Settings Copied!');
+							ajaxify.refresh();
+						});
+
+						modal.modal('hide');
+						return false;
+					}
 
-			uploader.open(RELATIVE_PATH + '/api/admin/category/uploadpicture', { cid: cid }, 0, function(imageUrlOnServer) {
-				inputEl.val(imageUrlOnServer);
+					var modal = bootbox.dialog({
+						title: 'Select a Category',
+						message: html,
+						buttons: {
+							save: {
+								label: 'Copy',
+								className: 'btn-primary',
+								callback: submit
+							}
+						}
+					});
+
+					modal.find('form').on('submit', submit);
+				});
+			});
+		});
+
+		$('.upload-button').on('click', function() {
+			var inputEl = $(this);
+			var cid = inputEl.attr('data-cid');
+
+			uploader.show({
+				title: 'Upload category image',
+				route: config.relative_path + '/api/admin/category/uploadpicture',
+				params: {cid: cid}
+			}, function(imageUrlOnServer) {
+				$('#category-image').val(imageUrlOnServer);
 				var previewBox = inputEl.parent().parent().siblings('.category-preview');
 				previewBox.css('background', 'url(' + imageUrlOnServer + '?' + new Date().getTime() + ')');
-				modified(inputEl[0]);
+
+				modified($('#category-image'));
 			});
 		});
 
+		$('#category-image').on('change', function() {
+			$('.category-preview').css('background-image', $(this).val() ? ('url("' + $(this).val() + '")') : '');
+		});
+
 		$('.delete-image').on('click', function(e) {
 			e.preventDefault();
 
-			var inputEl = $('.upload-button'),
-				previewBox = inputEl.parent().parent().siblings('.category-preview');
+			var inputEl = $('#category-image');
+			var previewBox = $('.category-preview');
 
 			inputEl.val('');
 			previewBox.css('background-image', '');
@@ -124,7 +175,7 @@ define('admin/manage/category', [
 			$(this).parent().addClass('hide').hide();
 		});
 
-		$('.category-preview').on('click', function(ev) {
+		$('.category-preview').on('click', function() {
 			iconSelect.init($(this).find('i'), modified);
 		});
 
@@ -146,12 +197,6 @@ define('admin/manage/category', [
 		});
 
 		Category.setupPrivilegeTable();
-		
-		if (window.location.hash === '#analytics') {
-			Category.setupGraphs();
-		} else {
-			$('a[href="#analytics"]').on('shown.bs.tab', Category.setupGraphs);
-		}
 	};
 
 	Category.setupPrivilegeTable = function() {
@@ -352,106 +397,5 @@ define('admin/manage/category', [
 		});
 	};
 
-	Category.setupGraphs = function() {
-		var hourlyCanvas = document.getElementById('pageviews:hourly'),
-			dailyCanvas = document.getElementById('pageviews:daily'),
-			topicsCanvas = document.getElementById('topics:daily'),
-			postsCanvas = document.getElementById('posts:daily'),
-			hourlyLabels = utils.getHoursArray().map(function(text, idx) {
-				return idx % 3 ? '' : text;
-			}),
-			dailyLabels = utils.getDaysArray().map(function(text, idx) {
-				return idx % 3 ? '' : text;
-			});
-
-		if (utils.isMobile()) {
-			Chart.defaults.global.showTooltips = false;
-		}
-
-		var data = {
-			'pageviews:hourly': {
-				labels: hourlyLabels,
-				datasets: [
-					{
-						label: "",
-						fillColor: "rgba(186,139,175,0.2)",
-						strokeColor: "rgba(186,139,175,1)",
-						pointColor: "rgba(186,139,175,1)",
-						pointStrokeColor: "#fff",
-						pointHighlightFill: "#fff",
-						pointHighlightStroke: "rgba(186,139,175,1)",
-						data: ajaxify.data.analytics['pageviews:hourly']
-					}
-				]
-			},
-			'pageviews:daily': {
-				labels: dailyLabels,
-				datasets: [
-					{
-						label: "",
-						fillColor: "rgba(151,187,205,0.2)",
-						strokeColor: "rgba(151,187,205,1)",
-						pointColor: "rgba(151,187,205,1)",
-						pointStrokeColor: "#fff",
-						pointHighlightFill: "#fff",
-						pointHighlightStroke: "rgba(151,187,205,1)",
-						data: ajaxify.data.analytics['pageviews:daily']
-					}
-				]
-			},
-			'topics:daily': {
-				labels: dailyLabels.slice(-7),
-				datasets: [
-					{
-						label: "",
-						fillColor: "rgba(171,70,66,0.2)",
-						strokeColor: "rgba(171,70,66,1)",
-						pointColor: "rgba(171,70,66,1)",
-						pointStrokeColor: "#fff",
-						pointHighlightFill: "#fff",
-						pointHighlightStroke: "rgba(171,70,66,1)",
-						data: ajaxify.data.analytics['topics:daily']
-					}
-				]
-			},
-			'posts:daily': {
-				labels: dailyLabels.slice(-7),
-				datasets: [
-					{
-						label: "",
-						fillColor: "rgba(161,181,108,0.2)",
-						strokeColor: "rgba(161,181,108,1)",
-						pointColor: "rgba(161,181,108,1)",
-						pointStrokeColor: "#fff",
-						pointHighlightFill: "#fff",
-						pointHighlightStroke: "rgba(161,181,108,1)",
-						data: ajaxify.data.analytics['posts:daily']
-					}
-				]
-			},
-		};
-
-		hourlyCanvas.width = $(hourlyCanvas).parent().width();
-		dailyCanvas.width = $(dailyCanvas).parent().width();
-		topicsCanvas.width = $(topicsCanvas).parent().width();
-		postsCanvas.width = $(postsCanvas).parent().width();
-		new Chart(hourlyCanvas.getContext('2d')).Line(data['pageviews:hourly'], {
-			responsive: true,
-			animation: false
-		});
-		new Chart(dailyCanvas.getContext('2d')).Line(data['pageviews:daily'], {
-			responsive: true,
-			animation: false
-		});
-		new Chart(topicsCanvas.getContext('2d')).Line(data['topics:daily'], {
-			responsive: true,
-			animation: false
-		});
-		new Chart(postsCanvas.getContext('2d')).Line(data['posts:daily'], {
-			responsive: true,
-			animation: false
-		});
-	};
-
 	return Category;
 });
\ No newline at end of file
diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js
index 58c5017540..06284e4e88 100644
--- a/public/src/admin/manage/group.js
+++ b/public/src/admin/manage/group.js
@@ -98,7 +98,7 @@ define('admin/manage/group', [
 
 				templates.parse('partials/groups/memberlist', 'members', {group: {isOwner: ajaxify.data.group.isOwner, members: [member]}}, function(html) {
 					translator.translate(html, function(html) {
-						$('[component="groups/members"] tr').first().before(html);
+						$('[component="groups/members"] tbody').prepend(html);
 					});
 				});
 			});
diff --git a/public/src/admin/manage/ip-blacklist.js b/public/src/admin/manage/ip-blacklist.js
new file mode 100644
index 0000000000..9769b3e3bb
--- /dev/null
+++ b/public/src/admin/manage/ip-blacklist.js
@@ -0,0 +1,40 @@
+'use strict';
+/* globals $, app, socket, templates, define, bootbox */
+
+define('admin/manage/ip-blacklist', [], function() {
+
+	var Blacklist = {};
+
+	Blacklist.init = function() {
+		var blacklist = $('#blacklist-rules');
+
+		blacklist.on('keyup', function() {
+		    $('#blacklist-rules-holder').val(blacklist.val());
+		});
+
+		$('[data-action="apply"]').on('click', function() {
+			socket.emit('blacklist.save', blacklist.val(), function(err) {
+				if (err) {
+					return app.alertError(err.message);
+				}
+				app.alert({
+					type: 'success',
+					alert_id: 'blacklist-saved',
+					title: 'Blacklist Applied',
+				});
+			});
+		});
+
+		$('[data-action="test"]').on('click', function() {
+			socket.emit('blacklist.validate', {
+				rules: blacklist.val()
+			}, function(err, data) {
+				templates.parse('admin/partials/blacklist-validate', data, function(html) {
+					bootbox.alert(html);
+				});
+			});
+		});
+	};
+
+	return Blacklist;
+});
\ No newline at end of file
diff --git a/public/src/admin/manage/registration.js b/public/src/admin/manage/registration.js
index 0592fc02a9..55578901e8 100644
--- a/public/src/admin/manage/registration.js
+++ b/public/src/admin/manage/registration.js
@@ -8,7 +8,6 @@ define('admin/manage/registration', function() {
 	Registration.init = function() {
 
 		$('.users-list').on('click', '[data-action]', function(ev) {
-			var $this = this;
 			var parent = $(this).parents('[data-username]');
 			var action = $(this).attr('data-action');
 			var username = parent.attr('data-username');
@@ -22,6 +21,37 @@ define('admin/manage/registration', function() {
 			});
 			return false;
 		});
+
+		$('.invites-list').on('click', '[data-action]', function(ev) {
+			var parent = $(this).parents('[data-invitation-mail][data-invited-by]');
+			var email = parent.attr('data-invitation-mail');
+			var invitedBy = parent.attr('data-invited-by');
+			var action = $(this).attr('data-action');
+			var method = 'admin.user.deleteInvitation';
+
+			var removeRow = function () {
+				var nextRow = parent.next(),
+					thisRowinvitedBy = parent.find('.invited-by'),
+					nextRowInvitedBy = nextRow.find('.invited-by');
+				if (nextRowInvitedBy.html() !== undefined && nextRowInvitedBy.html().length < 2) {
+					nextRowInvitedBy.html(thisRowinvitedBy.html());
+				}
+				parent.remove();
+			};
+			if (action === 'delete') {
+				bootbox.confirm('Are you sure you wish to delete this invitation?', function(confirm) {
+					if (confirm) {
+						socket.emit(method, {email: email, invitedBy: invitedBy}, function(err) {
+							if (err) {
+								return app.alertError(err.message);
+							}
+							removeRow();
+						});
+					}
+				});
+			}
+			return false;
+		});
 	};
 
 	return Registration;
diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js
index 273b7be0a3..c4b880f761 100644
--- a/public/src/admin/settings.js
+++ b/public/src/admin/settings.js
@@ -111,10 +111,12 @@ define('admin/settings', ['uploader', 'sounds'], function(uploader, sounds) {
 			var uploadBtn = $(this);
 			uploadBtn.on('click', function() {
 				uploader.show({
+					title: uploadBtn.attr('data-title'),
+					description: uploadBtn.attr('data-description'),
 					route: uploadBtn.attr('data-route'),
 					params: {},
-					fileSize: 0,
-					showHelp: uploadBtn.attr('data-help') ? uploadBtn.attr('data-help') === 1 : undefined
+					showHelp: uploadBtn.attr('data-help') ? uploadBtn.attr('data-help') === 1 : undefined,
+					accept: uploadBtn.attr('data-accept')
 				}, function(image) {
 					// need to move these into template, ex data-callback
 					if (ajaxify.currentPage === 'admin/general/sounds') {
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index c6b9418693..b72447a980 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -4,13 +4,14 @@ var ajaxify = ajaxify || {};
 
 $(document).ready(function() {
 
-	/*global app, templates, utils, socket, config, RELATIVE_PATH*/
+	/*global app, templates, socket, config, RELATIVE_PATH*/
 
-	var location = document.location || window.location,
-		rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
-		apiXHR = null,
+	var location = document.location || window.location;
+	var rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : '');
+	var apiXHR = null;
 
-		translator;
+	var translator;
+	var retry = true;
 
 	// Dumb hack to fool ajaxify into thinking translator is still a global
 	// When ajaxify is migrated to a require.js module, then this can be merged into the "define" call
@@ -21,10 +22,16 @@ $(document).ready(function() {
 	$(window).on('popstate', function (ev) {
 		ev = ev.originalEvent;
 
-		if (ev !== null && ev.state && ev.state.url !== undefined) {
-			ajaxify.go(ev.state.url, function() {
-				$(window).trigger('action:popstate', {url: ev.state.url});
-			}, true);
+		if (ev !== null && ev.state) {
+			if (ev.state.url === null && ev.state.returnPath !== undefined) {
+				window.history.replaceState({
+					url: ev.state.returnPath
+				}, ev.state.returnPath, config.relative_path + '/' + ev.state.returnPath);
+			} else if (ev.state.url !== undefined) {
+				ajaxify.go(ev.state.url, function() {
+					$(window).trigger('action:popstate', {url: ev.state.url});
+				}, true);
+			}
 		}
 	});
 
@@ -63,7 +70,7 @@ $(document).ready(function() {
 			if (err) {
 				return onAjaxError(err, url, callback, quiet);
 			}
-
+			retry = true;
 			app.template = data.template.name;
 
 			require(['translator'], function(translator) {
@@ -96,7 +103,7 @@ $(document).ready(function() {
 			app.previousUrl = window.location.href;
 		}
 
-		ajaxify.currentPage = url;
+		ajaxify.currentPage = url.split(/[?#]/)[0];
 
 		if (window.history && window.history.pushState) {
 			window.history[!quiet ? 'pushState' : 'replaceState']({
@@ -107,20 +114,25 @@ $(document).ready(function() {
 	};
 
 	function onAjaxError(err, url, callback, quiet) {
-		var data = err.data,
-			textStatus = err.textStatus;
+		var data = err.data;
+		var textStatus = err.textStatus;
 
 		if (data) {
 			var status = parseInt(data.status, 10);
 			if (status === 403 || status === 404 || status === 500 || status === 502 || status === 503) {
+				if (status === 502 && retry) {
+					retry = false;
+					return ajaxify.go(url, callback, quiet);
+				}
 				if (status === 502) {
 					status = 500;
 				}
 				if (data.responseJSON) {
 					data.responseJSON.config = config;
 				}
+
 				$('#footer, #content').removeClass('hide').addClass('ajaxifying');
-				return renderTemplate(url, status.toString(), data.responseJSON, callback);
+				return renderTemplate(url, status.toString(), data.responseJSON || {}, callback);
 			} else if (status === 401) {
 				app.alertError('[[global:please_log_in]]');
 				app.previousUrl = url;
@@ -142,6 +154,7 @@ $(document).ready(function() {
 
 		templates.parse(tpl_url, data, function(template) {
 			translator.translate(template, function(translatedTemplate) {
+				translatedTemplate = translator.unescape(translatedTemplate);
 				$('body').addClass(data.bodyClass);
 				$('#content').html(translatedTemplate);
 
@@ -189,7 +202,7 @@ $(document).ready(function() {
 			e.preventDefault();
 		}
 
-		ajaxify.go(ajaxify.currentPage, callback, true);
+		ajaxify.go(ajaxify.currentPage + window.location.search + window.location.hash, callback, true);
 	};
 
 	ajaxify.loadScript = function(tpl_url, callback) {
@@ -274,17 +287,26 @@ $(document).ready(function() {
 		$(document.body).on('click', 'a', function (e) {
 			if (this.target !== '' || (this.protocol !== 'http:' && this.protocol !== 'https:')) {
 				return;
-			} else if (hrefEmpty(this.href) || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false' || $(this).attr('href') === '#') {
+			}
+
+			var internalLink = this.host === '' ||	// Relative paths are always internal links
+				(this.host === window.location.host && this.protocol === window.location.protocol &&	// Otherwise need to check if protocol and host match
+				(RELATIVE_PATH.length > 0 ? this.pathname.indexOf(RELATIVE_PATH) === 0 : true));	// Subfolder installs need this additional check
+
+			if ($(this).attr('data-ajaxify') === 'false') {
+				if (!internalLink) {
+					return;
+				} else {
+					return e.preventDefault();
+				}
+			}
+
+			if (hrefEmpty(this.href) || this.protocol === 'javascript:' || $(this).attr('href') === '#') {
 				return e.preventDefault();
 			}
 
 			if (!e.ctrlKey && !e.shiftKey && !e.metaKey && e.which === 1) {
-				if (
-					this.host === '' ||	// Relative paths are always internal links...
-					(this.host === window.location.host && this.protocol === window.location.protocol &&	// Otherwise need to check that protocol and host match
-					(RELATIVE_PATH.length > 0 ? this.pathname.indexOf(RELATIVE_PATH) === 0 : true))	// Subfolder installs need this additional check
-				) {
-					// Internal link
+				if (internalLink) {
 					var pathname = this.href.replace(rootUrl + RELATIVE_PATH + '/', '');
 
 					// Special handling for urls with hashes
@@ -296,7 +318,6 @@ $(document).ready(function() {
 						}
 					}
 				} else if (window.location.pathname !== '/outgoing') {
-					// External Link
 					if (config.openOutgoingLinksInNewTab) {
 						window.open(this.href, '_blank');
 						e.preventDefault();
diff --git a/public/src/app.js b/public/src/app.js
index dc66cfc462..791aabd628 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -24,65 +24,65 @@ app.cacheBuster = null;
 	});
 
 	app.load = function() {
-		$('document').ready(function () {
-			var url = ajaxify.start(window.location.pathname.slice(1) + window.location.search + window.location.hash, true);
-			ajaxify.end(url, app.template);
+		app.loadProgressiveStylesheet();
 
-			handleStatusChange();
+		var url = ajaxify.start(window.location.pathname.slice(1) + window.location.search + window.location.hash, true);
+		ajaxify.end(url, app.template);
 
-			if (config.searchEnabled) {
-				app.handleSearch();
-			}
+		handleStatusChange();
 
-			$('#content').on('click', '#new_topic', function(){
-				app.newTopic();
-			});
+		if (config.searchEnabled) {
+			app.handleSearch();
+		}
 
-			require(['components'], function(components) {
-				components.get('user/logout').on('click', app.logout);
-			});
+		$('#content').on('click', '#new_topic', function(){
+			app.newTopic();
+		});
 
-			Visibility.change(function(e, state){
-				if (state === 'visible') {
-					app.isFocused = true;
-					app.alternatingTitle('');
-				} else if (state === 'hidden') {
-					app.isFocused = false;
-				}
-			});
+		require(['components'], function(components) {
+			components.get('user/logout').on('click', app.logout);
+		});
 
-			overrides.overrideBootbox();
-			overrides.overrideTimeago();
-			createHeaderTooltips();
-			app.showEmailConfirmWarning();
-
-			socket.removeAllListeners('event:nodebb.ready');
-			socket.on('event:nodebb.ready', function(data) {
-				if (!app.cacheBusters || app.cacheBusters['cache-buster'] !== data['cache-buster']) {
-					app.cacheBusters = data;
-
-					app.alert({
-						alert_id: 'forum_updated',
-						title: '[[global:updated.title]]',
-						message: '[[global:updated.message]]',
-						clickfn: function() {
-							window.location.reload();
-						},
-						type: 'warning'
-					});
-				}
-			});
+		Visibility.change(function(e, state){
+			if (state === 'visible') {
+				app.isFocused = true;
+				app.alternatingTitle('');
+			} else if (state === 'hidden') {
+				app.isFocused = false;
+			}
+		});
 
-			require(['taskbar', 'helpers', 'forum/pagination'], function(taskbar, helpers, pagination) {
-				taskbar.init();
+		overrides.overrideBootbox();
+		overrides.overrideTimeago();
+		createHeaderTooltips();
+		app.showEmailConfirmWarning();
 
-				// templates.js helpers
-				helpers.register();
+		socket.removeAllListeners('event:nodebb.ready');
+		socket.on('event:nodebb.ready', function(data) {
+			if (!app.cacheBusters || app.cacheBusters['cache-buster'] !== data['cache-buster']) {
+				app.cacheBusters = data;
 
-				pagination.init();
+				app.alert({
+					alert_id: 'forum_updated',
+					title: '[[global:updated.title]]',
+					message: '[[global:updated.message]]',
+					clickfn: function() {
+						window.location.reload();
+					},
+					type: 'warning'
+				});
+			}
+		});
 
-				$(window).trigger('action:app.load');
-			});
+		require(['taskbar', 'helpers', 'forum/pagination'], function(taskbar, helpers, pagination) {
+			taskbar.init();
+
+			// templates.js helpers
+			helpers.register();
+
+			pagination.init();
+
+			$(window).trigger('action:app.load');
 		});
 	};
 
@@ -134,13 +134,7 @@ app.cacheBuster = null;
 		callback = callback || function() {};
 		if (socket && app.user.uid && app.currentRoom !== room) {
 			socket.emit('meta.rooms.enter', {
-				enter: room,
-				username: app.user.username,
-				userslug: app.user.userslug,
-				picture: app.user.picture,
-				status: app.user.status,
-				'icon:bgColor': app.user['icon:bgColor'],
-				'icon:text': app.user['icon:text']
+				enter: room
 			}, function(err) {
 				if (err) {
 					return app.alertError(err.message);
@@ -332,7 +326,10 @@ app.cacheBuster = null;
 			return;
 		}
 		require(['translator'], function(translator) {
-			title = config.titleLayout.replace(/&#123;/g, '{').replace(/&#125;/g, '}').replace('{pageTitle}', title).replace('{browserTitle}', config.browserTitle);
+			title = config.titleLayout.replace(/&#123;/g, '{').replace(/&#125;/g, '}')
+				.replace('{pageTitle}', function() { return title; })
+				.replace('{browserTitle}', function() { return config.browserTitle; });
+
 			translator.translate(title, function(translated) {
 				titleObj.titles[0] = translated;
 				app.alternatingTitle('');
@@ -527,6 +524,7 @@ app.cacheBuster = null;
 			if (typeof blockName === 'string') {
 				templates.parse(template, blockName, data, function(html) {
 					translator.translate(html, function(translatedHTML) {
+						translatedHTML = translator.unescape(translatedHTML);
 						callback($(translatedHTML));
 					});
 				});
@@ -534,10 +532,19 @@ app.cacheBuster = null;
 				callback = data, data = blockName;
 				templates.parse(template, data, function(html) {
 					translator.translate(html, function(translatedHTML) {
+						translatedHTML = translator.unescape(translatedHTML);
 						callback($(translatedHTML));
 					});
 				});
 			}
 		});
 	};
+
+	app.loadProgressiveStylesheet = function() {
+		var linkEl = document.createElement('link');
+		linkEl.rel = 'stylesheet';
+		linkEl.href = config.relative_path + '/js-enabled.css';
+
+		document.head.appendChild(linkEl);
+	}
 }());
diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js
index cd97196ad0..de5b01e809 100644
--- a/public/src/client/account/edit.js
+++ b/public/src/client/account/edit.js
@@ -4,8 +4,7 @@
 
 define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], function(header, uploader, translator) {
 	var AccountEdit = {},
-		uploadedPicture = '',
-		selectedImageType = '';
+		uploadedPicture = '';
 
 	AccountEdit.init = function() {
 		uploadedPicture = ajaxify.data.uploadedpicture;
@@ -18,7 +17,8 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 			$('#inputBirthday').datepicker({
 				changeMonth: true,
 				changeYear: true,
-				yearRange: '1900:+0'
+				yearRange: '1900:-5y',
+				defaultDate: '-13y'
 			});
 		});
 
@@ -78,10 +78,15 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 				if (err) {
 					return app.alertError(err.message);
 				}
-			
+
+				// boolean to signify whether an uploaded picture is present in the pictures list
+				var uploaded = pictures.reduce(function(memo, cur) {
+					return memo || cur.type === 'uploaded';
+				}, false);
+
 				templates.parse('partials/modals/change_picture_modal', {
 					pictures: pictures,
-					uploaded: !!ajaxify.data.uploadedpicture,
+					uploaded: uploaded,
 					allowProfileImageUploads: ajaxify.data.allowProfileImageUploads
 				}, function(html) {
 					translator.translate(html, function(html) {
@@ -104,12 +109,15 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 						});
 
 						modal.on('shown.bs.modal', updateImages);
-						modal.on('click', '.list-group-item', selectImageType);
+						modal.on('click', '.list-group-item', function selectImageType() {
+							modal.find('.list-group-item').removeClass('active');
+							$(this).addClass('active');
+						});
+
 						handleImageUpload(modal);
 
 						function updateImages() {
-							var currentPicture = $('#user-current-picture').attr('src'),
-								userIcon = modal.find('.user-icon');
+							var userIcon = modal.find('.user-icon');
 
 							userIcon
 								.css('background-color', ajaxify.data['icon:bgColor'])
@@ -123,15 +131,10 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 									if (this.getAttribute('src') === ajaxify.data.picture) {
 										$(this).parents('.list-group-item').addClass('active');
 									}
-								})
+								});
 							}
 						}
 
-						function selectImageType() {
-							modal.find('.list-group-item').removeClass('active');
-							$(this).addClass('active');
-						}
-
 						function saveSelection() {
 							var type = modal.find('.list-group-item.active').attr('data-type'),
 								src = modal.find('.list-group-item.active img').attr('src');
@@ -189,20 +192,34 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 		function onUploadComplete(urlOnServer) {
 			urlOnServer = urlOnServer + '?' + new Date().getTime();
 
-			$('#user-current-picture, img.avatar').attr('src', urlOnServer);
 			updateHeader(urlOnServer);
-			uploadedPicture = urlOnServer;
+
+			if (ajaxify.data.picture.length) {
+				$('#user-current-picture, img.avatar').attr('src', urlOnServer);
+				uploadedPicture = urlOnServer;
+			} else {
+				ajaxify.refresh();
+			}
 		}
 
-		function onRemoveComplete(urlOnServer) {
-			$('#user-current-picture').attr('src', urlOnServer);
-			updateHeader(urlOnServer);
-			uploadedPicture = '';
+		function onRemoveComplete() {
+			if (ajaxify.data.uploadedpicture === ajaxify.data.picture) {
+				ajaxify.refresh();
+				updateHeader();
+			}
 		}
 
 		modal.find('[data-action="upload"]').on('click', function() {
 			modal.modal('hide');
-			uploader.open(config.relative_path + '/api/user/' + ajaxify.data.userslug + '/uploadpicture', {}, ajaxify.data.maximumProfileImageSize, function(imageUrlOnServer) {
+
+			uploader.show({
+				route: config.relative_path + '/api/user/' + ajaxify.data.userslug + '/uploadpicture',
+				params: {},
+				fileSize: ajaxify.data.maximumProfileImageSize,
+				title: '[[user:upload_picture]]',
+				description: '[[user:upload_a_picture]]',
+				accept: '.png,.jpg,.bmp'
+			}, function(imageUrlOnServer) {
 				onUploadComplete(imageUrlOnServer);
 			});
 
@@ -239,12 +256,12 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 		});
 
 		modal.find('[data-action="remove-uploaded"]').on('click', function() {
-			socket.emit('user.removeUploadedPicture', {uid: ajaxify.data.theirid}, function(err, imageUrlOnServer) {
+			socket.emit('user.removeUploadedPicture', {uid: ajaxify.data.theirid}, function(err) {
 				modal.modal('hide');
 				if (err) {
 					return app.alertError(err.message);
 				}
-				onRemoveComplete(imageUrlOnServer);
+				onRemoveComplete();
 			});
 		});
 	}
diff --git a/public/src/client/account/edit/email.js b/public/src/client/account/edit/email.js
index 4014039740..1772f76c49 100644
--- a/public/src/client/account/edit/email.js
+++ b/public/src/client/account/edit/email.js
@@ -19,6 +19,10 @@ define('forum/account/edit/email', ['forum/account/header'], function(header) {
 				return;
 			}
 
+			if (userData.email === userData.password) {
+				return app.alertError('[[user:email_same_as_password]]');
+			}
+
 			var btn = $(this);
 			btn.addClass('disabled').find('i').removeClass('hide');
 
diff --git a/public/src/client/account/edit/password.js b/public/src/client/account/edit/password.js
index d5e500e7df..3dc36c25b2 100644
--- a/public/src/client/account/edit/password.js
+++ b/public/src/client/account/edit/password.js
@@ -21,12 +21,15 @@ define('forum/account/edit/password', ['forum/account/header', 'translator'], fu
 		var passwordsmatch = false;
 
 		function onPasswordChanged() {
+			passwordvalid = false;
 			if (password.val().length < ajaxify.data.minimumPasswordLength) {
 				showError(password_notify, '[[user:change_password_error_length]]');
-				passwordvalid = false;
 			} else if (!utils.isPasswordValid(password.val())) {
 				showError(password_notify, '[[user:change_password_error]]');
-				passwordvalid = false;
+			} else if (password.val() === ajaxify.data.username) {
+				showError(password_notify, '[[user:password_same_as_username]]');
+			} else if (password.val() === ajaxify.data.email) {
+				showError(password_notify, '[[user:password_same_as_email]]');
 			} else {
 				showSuccess(password_notify);
 				passwordvalid = true;
diff --git a/public/src/client/account/edit/username.js b/public/src/client/account/edit/username.js
index 4448568157..64f9baa0bc 100644
--- a/public/src/client/account/edit/username.js
+++ b/public/src/client/account/edit/username.js
@@ -18,6 +18,11 @@ define('forum/account/edit/username', ['forum/account/header'], function(header)
 			if (!userData.username) {
 				return;
 			}
+
+			if (userData.username === userData.password) {
+				return app.alertError('[[user:username_same_as_password]]');
+			}
+
 			var btn = $(this);
 			btn.addClass('disabled').find('i').removeClass('hide');
 			socket.emit('user.changeUsernameEmail', userData, function(err, data) {
diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js
index 6449ac839f..520224d9ef 100644
--- a/public/src/client/account/header.js
+++ b/public/src/client/account/header.js
@@ -74,7 +74,12 @@ define('forum/account/header', [
 				}, callback);
 			},
 			function() {
-				uploader.open(config.relative_path + '/api/user/' + ajaxify.data.userslug + '/uploadcover', { uid: yourid }, 0, function(imageUrlOnServer) {
+				uploader.show({
+					title: '[[user:upload_cover_picture]]',
+					route: config.relative_path + '/api/user/' + ajaxify.data.userslug + '/uploadcover',
+					params: {uid: yourid },
+					accept: '.png,.jpg,.bmp'
+				}, function(imageUrlOnServer) {
 					components.get('account/cover').css('background-image', 'url(' + imageUrlOnServer + '?v=' + Date.now() + ')');
 				});
 			},
diff --git a/public/src/client/categories.js b/public/src/client/categories.js
index 03885a4e23..a73ed617bb 100644
--- a/public/src/client/categories.js
+++ b/public/src/client/categories.js
@@ -49,6 +49,7 @@ define('forum/categories', ['components', 'translator'], function(components, tr
 			html.fadeIn();
 
 			app.createUserTooltips();
+			html.find('.timeago').timeago();
 
 			if (category.find('[component="category/posts"]').length > parseInt(numRecentReplies, 10)) {
 				recentPosts.last().remove();
@@ -59,11 +60,11 @@ define('forum/categories', ['components', 'translator'], function(components, tr
 	}
 
 	function parseAndTranslate(posts, callback) {
-		templates.parse('categories', 'categories.posts', {categories: {posts: posts}}, function(html) {
+		templates.parse('categories', '(categories.)?posts', {categories: {posts: posts}}, function(html) {
 			translator.translate(html, function(translatedHTML) {
 				translatedHTML = $(translatedHTML);
 				translatedHTML.find('img:not(.not-responsive)').addClass('img-responsive');
-				translatedHTML.find('.timeago').timeago();
+
 				callback(translatedHTML);
 			});
 		});
diff --git a/public/src/client/chats.js b/public/src/client/chats.js
index 24c7a89192..672a12b57a 100644
--- a/public/src/client/chats.js
+++ b/public/src/client/chats.js
@@ -242,7 +242,7 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll',
 
 		if (data.users && data.users.length) {
 			data.users.forEach(function(user) {
-				tagEl.tagsinput('add', user.username);
+				tagEl.tagsinput('add', $('<div/>').html(user.username).text());
 			});
 		}
 
@@ -376,7 +376,7 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll',
 		Chats.onChatEdit();
 
 		socket.on('event:chats.roomRename', function(data) {
-			$('[component="chat/room/name"]').val(data.newName);
+			$('[component="chat/room/name"]').val($('<div/>').html(data.newName).text());
 		});
 	};
 
diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js
index de2fccd842..49f1d221f6 100644
--- a/public/src/client/groups/details.js
+++ b/public/src/client/groups/details.js
@@ -30,7 +30,11 @@ define('forum/groups/details', [
 					}, callback);
 				},
 				function() {
-					uploader.open(RELATIVE_PATH + '/api/groups/uploadpicture', { groupName: groupName }, 0, function(imageUrlOnServer) {
+					uploader.show({
+						title: '[[groups:upload-group-cover]]',
+						route: config.relative_path + '/api/groups/uploadpicture',
+						params: {groupName: groupName}
+					}, function(imageUrlOnServer) {
 						components.get('groups/cover').css('background-image', 'url(' + imageUrlOnServer + ')');
 					});
 				},
diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js
index 7d444bfd87..606a460dff 100644
--- a/public/src/client/groups/list.js
+++ b/public/src/client/groups/list.js
@@ -7,12 +7,6 @@ define('forum/groups/list', ['forum/infinitescroll'], function(infinitescroll) {
 	Groups.init = function() {
 		var groupsEl = $('#groups-list');
 
-		groupsEl.on('click', '.list-cover', function() {
-			var groupSlug = $(this).parents('[data-slug]').attr('data-slug');
-
-			ajaxify.go('groups/' + groupSlug);
-		});
-
 		infinitescroll.init(Groups.loadMoreGroups);
 
 		// Group creation
diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js
index b05c3a5a10..42443aa27e 100644
--- a/public/src/client/infinitescroll.js
+++ b/public/src/client/infinitescroll.js
@@ -23,6 +23,9 @@ define('forum/infinitescroll', ['translator'], function(translator) {
 	};
 
 	function onScroll() {
+		if (loadingMore) {
+			return;
+		}
 		var currentScrollTop = $(window).scrollTop();
 		var wh = $(window).height();
 		var viewportHeight = container.height() - wh;
diff --git a/public/src/client/search.js b/public/src/client/search.js
index c6bc30e44f..6ca5b8dc41 100644
--- a/public/src/client/search.js
+++ b/public/src/client/search.js
@@ -118,25 +118,25 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco
 			return;
 		}
 
-		try {
-			var regexStr = searchQuery.replace(/^"/, '').replace(/"$/, '').trim().split(' ').join('|');
-			var regex = new RegExp('(' + regexStr + ')', 'gi');
-
-			$('.search-result-text').each(function() {
-				var result = $(this);
+		var regexStr = searchQuery.replace(/^"/, '').replace(/"$/, '').trim().split(' ').join('|');
+		var regex = new RegExp('(' + regexStr + ')', 'gi');
 
-				var text = result.html().replace(regex, '<strong>$1</strong>');
-				result.html(text).find('img:not(.not-responsive)').addClass('img-responsive').each(function() {
-					$(this).attr('src', $(this).attr('src').replace(/<strong>([\s\S]*?)<\/strong>/gi, '$1'));
-				});
+		$('.search-result-text p, .search-result-text h4').each(function() {
+			var result = $(this), nested = [];
 
-				result.find('a').each(function() {
-					$(this).attr('href', $(this).attr('href').replace(/<strong>([\s\S]*?)<\/strong>/gi, '$1'));
-				});
+			result.find('*').each(function() {
+				$(this).after('<!-- ' + nested.length + ' -->');
+				nested.push($('<div />').append($(this)));
 			});
-		} catch(e) {
-			return;
-		}
+
+			result.html(result.html().replace(regex, '<strong>$1</strong>'));
+
+			for (var i = 0, ii = nested.length; i < ii; i++) {
+				result.html(result.html().replace('<!-- ' + i + ' -->', nested[i].html()));
+			}
+		});
+
+		$('.search-result-text').find('img:not(.not-responsive)').addClass('img-responsive');
 	}
 
 	function handleSavePreferences() {
diff --git a/public/src/client/topic.js b/public/src/client/topic.js
index 5772a570b3..ff0a255261 100644
--- a/public/src/client/topic.js
+++ b/public/src/client/topic.js
@@ -61,12 +61,12 @@ define('forum/topic', [
 
 		addParentHandler();
 
-		handleBookmark(tid);
-
 		handleKeys();
 
 		navigator.init('[component="post/anchor"]', ajaxify.data.postcount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback, Topic.calculateIndex);
 
+		handleBookmark(tid);
+
 		$(window).on('scroll', updateTopicTitle);
 
 		handleTopicSearch();
@@ -82,6 +82,9 @@ define('forum/topic', [
 
 	function onKeyDown(ev) {
 		if (ev.target.nodeName === 'BODY') {
+			if (ev.shiftKey || ev.ctrlKey || ev.altKey) {
+				return;
+			}
 			if (ev.which === 36) { // home key
 				Topic.toTop();
 				return false;
@@ -141,6 +144,7 @@ define('forum/topic', [
 				return navigator.scrollToPostIndex(postIndex, true);
 			}
 		} else if (bookmark && (!config.usePagination || (config.usePagination && ajaxify.data.pagination.currentPage === 1)) && ajaxify.data.postcount > 5) {
+			navigator.update(0);
 			app.alert({
 				alert_id: 'bookmark',
 				message: '[[topic:bookmark_instructions]]',
@@ -156,6 +160,8 @@ define('forum/topic', [
 			setTimeout(function() {
 				app.removeAlert('bookmark');
 			}, 10000);
+		} else {
+			navigator.update(0);
 		}
 	}
 
@@ -216,12 +222,14 @@ define('forum/topic', [
 
 	function updateTopicTitle() {
 		var span = components.get('navbar/title').find('span');
-		if ($(window).scrollTop() > 50) {
-			span.html(ajaxify.data.title).show();
-		} else {
-			span.html('').hide();
+		if ($(window).scrollTop() > 50 && span.hasClass('hidden')) {
+			span.html(ajaxify.data.title).removeClass('hidden');
+		} else if ($(window).scrollTop() <= 50 && !span.hasClass('hidden')) {
+			span.html('').addClass('hidden');
+		}
+		if ($(window).scrollTop() > 300) {
+			app.removeAlert('bookmark');
 		}
-		app.removeAlert('bookmark');
 	}
 
 	Topic.calculateIndex = function(index, elementCount) {
@@ -231,7 +239,7 @@ define('forum/topic', [
 		return index;
 	};
 
-	Topic.navigatorCallback = function(index, elementCount) {
+	Topic.navigatorCallback = function(index, elementCount, threshold) {
 		var path = ajaxify.removeRelativePath(window.location.pathname.slice(1));
 		if (!path.startsWith('topic')) {
 			return 1;
@@ -246,13 +254,13 @@ define('forum/topic', [
 				newUrl += '/' + index;
 			}
 
+			posts.loadImages(threshold);
+
 			if (newUrl !== currentUrl) {
 				if (Topic.replaceURLTimeout) {
 					clearTimeout(Topic.replaceURLTimeout);
 				}
-
 				Topic.replaceURLTimeout = setTimeout(function() {
-
 					updateUserBookmark(index);
 
 					Topic.replaceURLTimeout = 0;
diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js
index ba8c77598e..7f769f984b 100644
--- a/public/src/client/topic/events.js
+++ b/public/src/client/topic/events.js
@@ -114,17 +114,19 @@ define('forum/topic/events', [
 		}
 
 		editedPostEl.fadeOut(250, function() {
-			editedPostEl.html(data.post.content);
+			editedPostEl.html(translator.unescape(data.post.content));
 			editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive');
 			app.replaceSelfLinks(editedPostEl.find('a'));
 			posts.wrapImagesInLinks(editedPostEl.parent());
+			posts.unloadImages(editedPostEl.parent());
+			posts.loadImages();
 			editedPostEl.fadeIn(250);
 			$(window).trigger('action:posts.edited', data);
 		});
 
 		var editData = {
 			editor: data.editor,
-			relativeEditTime: utils.toISOString(data.post.edited)
+			editedISO: utils.toISOString(data.post.edited)
 		};
 
 		templates.parse('partials/topic/post-editor', editData, function(html) {
@@ -186,7 +188,7 @@ define('forum/topic/events', [
 			if (isDeleted) {
 				postEl.find('[component="post/content"]').translateHtml('[[topic:post_is_deleted]]');
 			} else {
-				postEl.find('[component="post/content"]').html(data.content);
+				postEl.find('[component="post/content"]').html(translator.unescape(data.content));
 			}
 		}
 	}
diff --git a/public/src/client/topic/move.js b/public/src/client/topic/move.js
index d013e1c09d..d16eb9bc41 100644
--- a/public/src/client/topic/move.js
+++ b/public/src/client/topic/move.js
@@ -6,8 +6,7 @@ define('forum/topic/move', function() {
 
 	var Move = {},
 		modal,
-		targetCid,
-		targetCategoryLabel;
+		selectedEl;
 
 	Move.init = function(tids, currentCid, onComplete) {
 		Move.tids = tids;
@@ -70,6 +69,8 @@ define('forum/topic/move', function() {
 		}
 		categoryEl.toggleClass('disabled', !!category.disabled);
 		categoryEl.attr('data-cid', category.cid);
+		categoryEl.attr('data-icon', category.icon);
+		categoryEl.attr('data-name', category.name);
 		categoryEl.html('<i class="fa fa-fw ' + category.icon + '"></i> ' + category.name);
 
 		parentEl.append(level);
@@ -88,15 +89,14 @@ define('forum/topic/move', function() {
 		modal.find('#confirm-category-name').html(category.html());
 		modal.find('#move-confirm').removeClass('hide');
 
-		targetCid = category.attr('data-cid');
-		targetCategoryLabel = category.html();
+		selectedEl = category;
 		modal.find('#move_thread_commit').prop('disabled', false);
 	}
 
 	function onCommitClicked() {
 		var commitEl = modal.find('#move_thread_commit');
 
-		if (!commitEl.prop('disabled') && targetCid) {
+		if (!commitEl.prop('disabled') && selectedEl.attr('data-cid')) {
 			commitEl.prop('disabled', true);
 
 			moveTopics();
@@ -106,7 +106,7 @@ define('forum/topic/move', function() {
 	function moveTopics() {
 		socket.emit(Move.moveAll ? 'topics.moveAll' : 'topics.move', {
 			tids: Move.tids,
-			cid: targetCid,
+			cid: selectedEl.attr('data-cid'),
 			currentCid: Move.currentCid
 		}, function(err) {
 			modal.modal('hide');
@@ -115,7 +115,7 @@ define('forum/topic/move', function() {
 				return app.alertError(err.message);
 			}
 
-			app.alertSuccess('[[topic:topic_move_success, ' + targetCategoryLabel + ']]');
+			app.alertSuccess('[[topic:topic_move_success, ' + selectedEl.attr('data-name') + ']] <i class="fa fa-fw ' + selectedEl.attr('data-icon') + '"></i>');
 			if (typeof Move.onComplete === 'function') {
 				Move.onComplete();
 			}
diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js
index d59cd38371..d1961a29a6 100644
--- a/public/src/client/topic/postTools.js
+++ b/public/src/client/topic/postTools.js
@@ -28,16 +28,17 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
 			var postEl = $this.parents('[data-pid]');
 			var pid = postEl.attr('data-pid');
 			var index = parseInt(postEl.attr('data-index'), 10);
+
 			socket.emit('posts.loadPostTools', {pid: pid, cid: ajaxify.data.cid}, function(err, data) {
 				if (err) {
 					return app.alertError(err);
 				}
 				data.posts.display_move_tools = data.posts.display_move_tools && index !== 0;
-				data.postSharing = data.postSharing.filter(function(share) { return share.activated === true; });
-				
+
 				templates.parse('partials/topic/post-menu-list', data, function(html) {
 					translator.translate(html, function(html) {
 						dropdownMenu.html(html);
+						$(window).trigger('action:post.tools.load');
 					});
 				});
 			});
@@ -63,27 +64,44 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
 	};
 
 	function addVoteHandler() {
-		components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', function() {
-			loadDataAndCreateTooltip($(this).parent());
+		components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', loadDataAndCreateTooltip);
+		components.get('topic').on('mouseout', '[data-pid] [component="post/vote-count"]', function() {
+			var el = $(this).parent();
+			el.on('shown.bs.tooltip', function() {
+				$('.tooltip').tooltip('destroy');
+				el.off('shown.bs.tooltip');
+			});
+
+			$('.tooltip').tooltip('destroy');
 		});
 	}
 
-	function loadDataAndCreateTooltip(el) {
+	function loadDataAndCreateTooltip(e) {
+		e.stopPropagation();
+
+		var $this = $(this);
+		var el = $this.parent();
 		var pid = el.parents('[data-pid]').attr('data-pid');
+
+		$('.tooltip').tooltip('destroy');
+		$this.off('mouseenter', loadDataAndCreateTooltip);
+
 		socket.emit('posts.getUpvoters', [pid], function(err, data) {
-			if (!err && data.length) {
+			if (err) {
+				return app.alertError(err.message);
+			}
+
+			if (data.length) {
 				createTooltip(el, data[0]);
 			}
+			$this.off('mouseenter').on('mouseenter', loadDataAndCreateTooltip);
 		});
+		return false;
 	}
 
 	function createTooltip(el, data) {
 		function doCreateTooltip(title) {
 			el.attr('title', title).tooltip('fixTitle').tooltip('show');
-			el.on('hidden.bs.tooltip', function() {
-				el.tooltip('destroy');
-				el.off('hidden.bs.tooltip');
-			});
 		}
 		var usernames = data.usernames;
 		if (!usernames.length) {
@@ -117,7 +135,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
 		});
 
 		$('.topic').on('click', '[component="topic/reply-as-topic"]', function() {
-			translator.translate('[[topic:link_back, ' + ajaxify.data.title + ', ' + config.relative_path + '/topic/' + ajaxify.data.slug + ']]', function(body) {
+			translator.translate('[[topic:link_back, ' + ajaxify.data.titleRaw + ', ' + config.relative_path + '/topic/' + ajaxify.data.slug + ']]', function(body) {
 				$(window).trigger('action:composer.topic.new', {
 					cid: ajaxify.data.cid,
 					body: body
@@ -179,14 +197,26 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
 	function onReplyClicked(button, tid) {
 		showStaleWarning(function(proceed) {
 			if (!proceed) {
-				var selectionText = '',
-					selection = window.getSelection ? window.getSelection() : document.selection.createRange();
-
-				if ($(selection.baseNode).parents('[component="post/content"]').length > 0) {
-					selectionText = selection.toString();
+				var selectionText = '';
+				var selection = window.getSelection ? window.getSelection() : document.selection.createRange();
+				var content = button.parents('[component="post"]').find('[component="post/content"]').get(0);
+
+				if (content && selection.containsNode(content, true)) {
+					var bounds = document.createRange();
+					bounds.selectNodeContents(content);
+					var range = selection.getRangeAt(0).cloneRange();
+					if (range.compareBoundaryPoints(Range.START_TO_START, bounds) < 0) {
+						range.setStart(bounds.startContainer, bounds.startOffset);
+					}
+					if (range.compareBoundaryPoints(Range.END_TO_END, bounds) > 0) {
+						range.setEnd(bounds.endContainer, bounds.endOffset);
+					}
+					bounds.detach();
+					selectionText = range.toString();
+					range.detach();
 				}
 
-				var username = getUserName(selectionText ? $(selection.baseNode) : button);
+				var username = getUserName(button);
 				if (getData(button, 'data-uid') === '0' || !getData(button, 'data-userslug')) {
 					username = '';
 				}
@@ -306,11 +336,13 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
 	}
 
 	function getUserName(button) {
-		var username = '',
-			post = button.parents('[data-pid]');
+		var username = '';
+		var post = button.parents('[data-pid]');
+
 		if (button.attr('component') === 'topic/reply') {
 			return username;
 		}
+
 		if (post.length) {
 			username = post.attr('data-username').replace(/\s/g, '-');
 		}
diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js
index 3059728e3b..696b2f51ec 100644
--- a/public/src/client/topic/posts.js
+++ b/public/src/client/topic/posts.js
@@ -10,7 +10,9 @@ define('forum/topic/posts', [
 	'components'
 ], function(pagination, infinitescroll, postTools, navigator, components) {
 
-	var Posts = {};
+	var Posts = {
+		_imageLoaderTimeout: undefined
+	};
 
 	Posts.onNewPost = function(data) {
 		if (!data || !data.posts || !data.posts.length) {
@@ -24,8 +26,9 @@ define('forum/topic/posts', [
 		data.loggedIn = app.user.uid ? true : false;
 		data.posts.forEach(function(post) {
 			post.selfPost = !!app.user.uid && parseInt(post.uid, 10) === parseInt(app.user.uid, 10);
-			post.display_moderator_tools = post.selfPost || ajaxify.data.isAdminOrMod;
-			post.display_move_tools = ajaxify.data.isAdminOrMod;
+			post.display_moderator_tools = post.selfPost || ajaxify.data.privileges.isAdminOrMod;
+			post.display_move_tools = ajaxify.data.privileges.isAdminOrMod;
+			post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || post.selfPost || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted);
 		});
 
 		updatePostCounts(data.posts);
@@ -61,7 +64,7 @@ define('forum/topic/posts', [
 
 		if (isPostVisible) {
 			createNewPosts(data, components.get('post').not('[data-index=0]'), direction, scrollToPost);
-		} else if (parseInt(posts[0].uid, 10) === parseInt(app.user.uid, 10)) {
+		} else if (ajaxify.data.scrollToMyPost && parseInt(posts[0].uid, 10) === parseInt(app.user.uid, 10)) {
 			pagination.loadPage(ajaxify.data.pagination.pageCount, scrollToPost);
 		}
 	}
@@ -78,6 +81,9 @@ define('forum/topic/posts', [
 	}
 
 	function scrollToPostIfSelf(post) {
+		if (!ajaxify.data.scrollToMyPost) {
+		    return;
+		}
 		var isSelfPost = parseInt(post.uid, 10) === parseInt(app.user.uid, 10);
 		if (isSelfPost) {
 			navigator.scrollBottom(post.index);
@@ -139,10 +145,16 @@ define('forum/topic/posts', [
 		}
 
 		data.slug = ajaxify.data.slug;
-		
+
 		$(window).trigger('action:posts.loading', {posts: data.posts, after: after, before: before});
 
 		app.parseAndTranslate('topic', 'posts', data, function(html) {
+
+			html = html.filter(function() {
+				var pid = $(this).attr('data-pid');
+				return pid && $('[component="post"][data-pid="' + pid + '"]').length === 0;
+			});
+
 			if (after) {
 				html.insertAfter(after);
 			} else if (before) {
@@ -169,10 +181,13 @@ define('forum/topic/posts', [
 	}
 
 	Posts.loadMorePosts = function(direction) {
-		if (!components.get('topic').length || navigator.scrollActive) {
+		if (!components.get('topic').length || navigator.scrollActive || Posts._infiniteScrollTimeout) {
 			return;
 		}
 
+		Posts._infiniteScrollTimeout = setTimeout(function() {
+			delete Posts._infiniteScrollTimeout;
+		}, 1000);
 		var replies = components.get('post').not('[data-index=0]').not('.new');
 		var afterEl = direction > 0 ? replies.last() : replies.first();
 		var after = parseInt(afterEl.attr('data-index'), 10) || 0;
@@ -192,7 +207,6 @@ define('forum/topic/posts', [
 			after: after,
 			direction: direction
 		}, function (data, done) {
-
 			indicatorEl.fadeOut();
 
 			if (data && data.posts && data.posts.length) {
@@ -208,6 +222,7 @@ define('forum/topic/posts', [
 	};
 
 	Posts.processPage = function(posts) {
+		Posts.unloadImages(posts);
 		Posts.showBottomPostBar();
 		posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive');
 		app.createUserTooltips(posts);
@@ -221,6 +236,82 @@ define('forum/topic/posts', [
 		hidePostToolsForDeletedPosts(posts);
 	};
 
+	Posts.unloadImages = function(posts) {
+		var images = posts.find('[component="post/content"] img:not(.not-responsive)');
+
+		images.each(function() {
+			$(this).attr('data-src', $(this).attr('src'));
+			$(this).attr('data-state', 'unloaded');
+			$(this).attr('src', 'about:blank');
+		});
+	};
+
+	Posts.loadImages = function(threshold) {
+		if (Posts._imageLoaderTimeout) {
+			clearTimeout(Posts._imageLoaderTimeout);
+		}
+
+		Posts._imageLoaderTimeout = setTimeout(function() {
+			/*
+				If threshold is defined, images loaded above this threshold will modify
+				the user's scroll position so they are not scrolled away from content
+				they were reading. Images loaded below this threshold will push down content.
+
+				If no threshold is defined, loaded images will push down content, as per
+				default
+			*/
+
+			var images = components.get('post/content').find('img[data-state="unloaded"]'),
+				visible = images.filter(function() {
+					return utils.isElementInViewport(this);
+				}),
+				scrollTop = $(window).scrollTop(),
+				adjusting = false,
+				adjustQueue = [],
+				adjustPosition = function() {
+					adjusting = true;
+					oldHeight = document.body.clientHeight;
+
+					// Display the image
+					$(this).attr('data-state', 'loaded');
+					newHeight = document.body.clientHeight;
+
+					var imageRect = this.getBoundingClientRect();
+					if (imageRect.top < threshold) {
+						scrollTop = scrollTop + (newHeight - oldHeight);
+						$(window).scrollTop(scrollTop);
+					}
+
+					if (adjustQueue.length) {
+						adjustQueue.pop()();
+					} else {
+						adjusting = false;
+					}
+				},
+				oldHeight, newHeight;
+
+			// For each image, reset the source and adjust scrollTop when loaded
+			visible.attr('data-state', 'loading');
+			visible.each(function(index, image) {
+				image = $(image);
+
+				image.on('load', function() {
+					if (!adjusting) {
+						adjustPosition.call(this);
+					} else {
+						adjustQueue.push(adjustPosition.bind(this));
+					}
+				});
+
+				image.attr('src', image.attr('data-src'));
+				if (image.parent().attr('href')) {
+					image.parent().attr('href', image.attr('data-src'));
+				}
+				image.removeAttr('data-src');
+			});
+		}, 250);
+	};
+
 	Posts.wrapImagesInLinks = function(posts) {
 		posts.find('[component="post/content"] img:not(.emoji)').each(function() {
 			var $this = $(this);
@@ -259,4 +350,4 @@ define('forum/topic/posts', [
 
 	return Posts;
 
-});
\ No newline at end of file
+});
diff --git a/public/src/modules/autocomplete.js b/public/src/modules/autocomplete.js
index ecf15c2ead..60a5550b8c 100644
--- a/public/src/modules/autocomplete.js
+++ b/public/src/modules/autocomplete.js
@@ -9,7 +9,7 @@ define('autocomplete', function() {
 	module.user = function (input, onselect) {
 		app.loadJQueryUI(function() {
 			input.autocomplete({
-				delay: 100,
+				delay: 200,
 				open: function() {
 					$(this).autocomplete('widget').css('z-index', 20000);
 				},
@@ -22,9 +22,10 @@ define('autocomplete', function() {
 
 						if (result && result.users) {
 							var names = result.users.map(function(user) {
+								var username = $('<div/>').html(user.username).text()
 								return user && {
-									label: user.username,
-									value: user.username,
+									label: username,
+									value: username,
 									user: {
 										uid: user.uid,
 										name: user.username,
@@ -44,7 +45,7 @@ define('autocomplete', function() {
 	module.group = function(input, onselect) {
 		app.loadJQueryUI(function() {
 			input.autocomplete({
-				delay: 100,
+				delay: 200,
 				select: onselect,
 				source: function(request, response) {
 					socket.emit('groups.search', {
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index 7721b6e70a..e860a0ff20 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -7,8 +7,8 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 	var newMessage = false;
 
 	module.prepareDOM = function() {
-		var	chatsToggleEl = components.get('chat/dropdown'),
-			chatsListEl = components.get('chat/list');
+		var chatsToggleEl = components.get('chat/dropdown');
+		var chatsListEl = components.get('chat/list');
 
 		chatsToggleEl.on('click', function() {
 			if (chatsToggleEl.parent().hasClass('open')) {
@@ -18,6 +18,14 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 			module.loadChatsDropdown(chatsListEl);
 		});
 
+		$('[component="chats/mark-all-read"]').on('click', function() {
+			socket.emit('modules.chats.markAllRead', function(err) {
+				if (err) {
+					return app.alertError(err);
+				}
+			});
+		});
+
 		socket.on('event:chats.receive', function(data) {
 			var username = data.message.fromUser.username;
 			var isSelf = data.self === 1;
@@ -42,7 +50,8 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 
 					taskbar.push('chat', modal.attr('UUID'), {
 						title: username,
-						touid: data.message.fromUser.uid
+						touid: data.message.fromUser.uid,
+						roomId: data.roomId
 					});
 				}
 			} else {
@@ -71,7 +80,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
 		});
 
 		socket.on('event:chats.roomRename', function(data) {
-			module.getModal(data.roomId).find('[component="chat/room/name"]').val(data.newName);
+			module.getModal(data.roomId).find('[component="chat/room/name"]').val($('<div/>').html(data.newName).text());
 		});
 
 		Chats.onChatEdit();
diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js
index 38737ceaa1..fda87711fb 100644
--- a/public/src/modules/helpers.js
+++ b/public/src/modules/helpers.js
@@ -1,6 +1,6 @@
 ;(function(exports) {
 	"use strict";
-	/* globals define, utils */
+	/* globals define, utils, config */
 
 	// export the class if we are in a Node-like system.
 	if (typeof module === 'object' && module.exports === exports) {
@@ -18,8 +18,9 @@
 
 		if (properties) {
 			if ((properties.loggedIn && !data.config.loggedIn) ||
+				(properties.globalMod && !data.isGlobalMod && !data.isAdmin) ||
 				(properties.adminOnly && !data.isAdmin) ||
-				(properties.installed && properties.installed.search && !data.searchEnabled)) {
+				(properties.searchInstalled && !data.searchEnabled)) {
 				return false;
 			}
 		}
@@ -99,6 +100,25 @@
 		return style.join('; ') + ';';
 	};
 
+	helpers.generateChildrenCategories = function(category) {
+		var html = '';
+		var relative_path = (typeof config !== 'undefined' ? config.relative_path : require('nconf').get('relative_path'));
+		
+		category.children.forEach(function(child) {
+			if (!child) {
+				return;
+			}
+			var link = child.link ? child.link : (relative_path + '/category/' + child.slug);
+			html += '<a href="' + link + '">' +
+					'<span class="fa-stack fa-lg">' +
+					'<i style="color:' + child.bgColor + ';" class="fa fa-circle fa-stack-2x"></i>' +
+					'<i style="color:' + child.color + ';" class="fa fa-stack-1x ' + child.icon + '"></i>' +
+					'</span><small>' + child.name + '</small></a> ';
+		});
+		html = html ? ('<span class="category-children">' + html + '</span>') : html;
+		return html;
+	};
+
 	helpers.generateTopicClass = function(topic) {
 		var style = [];
 
@@ -243,7 +263,7 @@
 		}
 
 		return icons;
-	}
+	};
 
 	exports.register = function() {
 		var templates;
diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js
index b17db23a14..3d65f6b8a0 100644
--- a/public/src/modules/navigator.js
+++ b/public/src/modules/navigator.js
@@ -1,7 +1,7 @@
 
 'use strict';
 
-/* globals app, define, ajaxify, utils, config */
+/* globals define, ajaxify, utils, config */
 
 
 define('navigator', ['forum/pagination', 'components'], function(pagination, components) {
@@ -56,7 +56,6 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
 		});
 
 		navigator.setCount(count);
-		navigator.update();
 	};
 
 	function generateUrl(index) {
@@ -92,13 +91,22 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
 		$('.pagination-block').toggleClass('ready', flag);
 	}
 
-	navigator.update = function() {
+	navigator.update = function(threshold) {
+		/*
+			The "threshold" is defined as the distance from the top of the page to
+			a spot where a user is expecting to begin reading.
+		*/
+		threshold = typeof threshold === 'number' ? threshold : undefined;
+
 		var els = $(navigator.selector);
 		if (els.length) {
 			index = parseInt(els.first().attr('data-index'), 10) + 1;
 		}
 
-		var middleOfViewport = $(window).scrollTop() + $(window).height() / 2;
+		var scrollTop = $(window).scrollTop();
+		var windowHeight = $(window).height();
+		var documentHeight = $(document).height();
+		var middleOfViewport = scrollTop + windowHeight / 2;
 		var previousDistance = Number.MAX_VALUE;
 		els.each(function() {
 			var distanceToMiddle = Math.abs(middleOfViewport - $(this).offset().top);
@@ -113,8 +121,28 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
 			}
 		});
 
+		var atTop = scrollTop === 0 && parseInt(els.first().attr('data-index'), 10) === 0,
+			nearBottom = scrollTop + windowHeight > documentHeight - 100 && parseInt(els.last().attr('data-index'), 10) === count - 1;
+
+		if (atTop) {
+			index = 1;
+		} else if (nearBottom) {
+			index = count;
+		}
+
+		// If a threshold is undefined, try to determine one based on new index
+		if (threshold === undefined) {
+			if (atTop) {
+				threshold = 0;
+			} else {
+				var anchorEl = components.get('post/anchor', index - 1);
+				var anchorRect = anchorEl.get(0).getBoundingClientRect();
+				threshold = anchorRect.top;
+			}
+		}
+
 		if (typeof navigator.callback === 'function') {
-			navigator.callback(index, count);
+			navigator.callback(index, count, threshold);
 		}
 
 		navigator.updateTextAndProgressBar();
@@ -190,30 +218,46 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
 	};
 
 	navigator.scrollToPostIndex = function(postIndex, highlight, duration) {
-		var scrollTo = components.get('post/anchor', postIndex);
+		var scrollTo = components.get('post/anchor', postIndex),
+			postEl = components.get('post', 'index', postIndex),
+			postHeight = postEl.height(),
+			viewportHeight = $(window).height(),
+			navbarHeight = components.get('navbar').height();
+
 
 		if (!scrollTo.length) {
 			navigator.scrollActive = false;
 			return;
 		}
 
+		// Temporarily disable navigator update on scroll
+		$(window).off('scroll', navigator.update);
+
 		duration = duration !== undefined ? duration : 400;
 		navigator.scrollActive = true;
 		var done = false;
 
 		function animateScroll() {
-			var scrollTop = (scrollTo.offset().top - ($(window).height() / 2)) + 'px';
+			var scrollTop = 0;
+			if (postHeight < viewportHeight) {
+				scrollTop = (scrollTo.offset().top - (viewportHeight / 2) + (postHeight / 2));
+			} else {
+				scrollTop = scrollTo.offset().top - navbarHeight;
+			}
 
 			$('html, body').animate({
-				scrollTop: scrollTop
+				scrollTop: scrollTop + 'px'
 			}, duration, function() {
 				if (done) {
+					// Re-enable onScroll behaviour
+					$(window).on('scroll', navigator.update);
+					var scrollToRect = scrollTo.get(0).getBoundingClientRect();
+					navigator.update(scrollToRect.top);
 					return;
 				}
 				done = true;
 
 				navigator.scrollActive = false;
-				navigator.update();
 				highlightPost();
 				$('body').scrollTop($('body').scrollTop() - 1);
 				$('html').scrollTop($('html').scrollTop() - 1);
@@ -225,7 +269,7 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
 				scrollTo.parents('[component="post"]').addClass('highlight');
 				setTimeout(function() {
 					scrollTo.parents('[component="post"]').removeClass('highlight');
-				}, 3000);
+				}, 10000);
 			}
 		}
 
diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js
index 7b57651867..35f1e677dd 100644
--- a/public/src/modules/notifications.js
+++ b/public/src/modules/notifications.js
@@ -1,10 +1,12 @@
 'use strict';
 
-/* globals define, socket, utils, config, app, ajaxify, templates, Tinycon*/
+/* globals define, socket, app, ajaxify, templates, Tinycon*/
 
 define('notifications', ['sounds', 'translator', 'components'], function(sound, translator, components) {
 	var Notifications = {};
 
+	var unreadNotifs = {};
+
 	Notifications.prepareDOM = function() {
 		var notifContainer = components.get('notifications'),
 			notifTrigger = notifContainer.children('a'),
@@ -29,34 +31,40 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound,
 
 		notifList.on('click', '[data-nid]', function() {
 			var unread = $(this).hasClass('unread');
+			var nid = $(this).attr('data-nid');
 			if (!unread) {
 				return;
 			}
-			socket.emit('notifications.markRead', $(this).attr('data-nid'), function(err) {
+			socket.emit('notifications.markRead', nid, function(err) {
 				if (err) {
 					return app.alertError(err.message);
 				}
 				incrementNotifCount(-1);
+				if (unreadNotifs[nid]) {
+					delete unreadNotifs[nid];
+				}
 			});
 		});
 
 		notifContainer.on('click', '.mark-all-read', Notifications.markAllRead);
 
-		notifList.on('click', '.mark-read', function(e) {
-			var liEl = $(this).parent(),
-				unread = liEl.hasClass('unread');
-
-			e.preventDefault();
-			e.stopPropagation();
+		notifList.on('click', '.mark-read', function() {
+			var liEl = $(this).parent();
+			var unread = liEl.hasClass('unread');
+			var nid = liEl.attr('data-nid');
 
-			socket.emit('notifications.mark' + (unread ? 'Read' : 'Unread'), liEl.attr('data-nid'), function(err) {
+			socket.emit('notifications.mark' + (unread ? 'Read' : 'Unread'), nid, function(err) {
 				if (err) {
 					return app.alertError(err.message);
 				}
 
 				liEl.toggleClass('unread');
 				incrementNotifCount(unread ? -1 : 1);
+				if (unread && unreadNotifs[nid]) {
+					delete unreadNotifs[nid];
+				}
 			});
+			return false;
 		});
 
 		function incrementNotifCount(delta) {
@@ -96,10 +104,13 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound,
 			if (ajaxify.currentPage === 'notifications') {
 				ajaxify.refresh();
 			}
+			
+			if (!unreadNotifs[notifData.nid]) {
+				incrementNotifCount(1);
 
-			incrementNotifCount(1);
-
-			sound.play('notification');
+				sound.play('notification');
+				unreadNotifs[notifData.nid] = true;	
+			}			
 		});
 
 		socket.on('event:notifications.updateCount', function(count) {
@@ -158,6 +169,7 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound,
 				app.alertError(err.message);
 			}
 			Notifications.updateNotifCount(0);
+			unreadNotifs = {};
 		});
 	};
 
diff --git a/public/src/modules/taskbar.js b/public/src/modules/taskbar.js
index e664037c0d..7f5978250b 100644
--- a/public/src/modules/taskbar.js
+++ b/public/src/modules/taskbar.js
@@ -55,7 +55,7 @@ define('taskbar', function() {
 
 		$(window).trigger('filter:taskbar.push', data);
 
-		if (!element.length) {
+		if (!element.length && data.module) {
 			createTaskbar(data);
 		}
 	};
diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js
index 58c8ef6f26..b208a0af48 100644
--- a/public/src/modules/translator.js
+++ b/public/src/modules/translator.js
@@ -213,17 +213,14 @@
 
 	function insertLanguage(text, key, value, variables) {
 		if (value) {
-			var variable;
-			for (var i = 1, ii = variables.length; i < ii; i++) {
-
-				// see https://github.com/NodeBB/NodeBB/issues/1951
-				variables[i] = variables[i].replace(/&#37;/g, '%').replace(/&#44;/g, ',');
-
-				variable = S(variables[i]).chompRight(']]').collapseWhitespace().escapeHTML().s;
-				value = value.replace('%' + i, variable);
-			}
+			variables.forEach(function(variable, index) {
+				if (index > 0) {
+					variable = S(variable).chompRight(']]').collapseWhitespace().decodeHTMLEntities().escapeHTML().s;
+					value = value.replace('%' + index, function() { return variable; });
+				}
+			});
 
-			text = text.replace(key, value);
+			text = text.replace(key, function() { return value; });
 		} else {
 			var string = key.split(':');
 			text = text.replace(key, string[string.length-1].replace(regexes.replace, ''));
diff --git a/public/src/modules/uploader.js b/public/src/modules/uploader.js
index ce66d37c17..6a0d7f161a 100644
--- a/public/src/modules/uploader.js
+++ b/public/src/modules/uploader.js
@@ -1,6 +1,6 @@
 'use strict';
 
-/* globals define, templates, translator */
+/* globals define, templates */
 
 define('uploader', ['csrf', 'translator'], function(csrf, translator) {
 
@@ -16,9 +16,14 @@ define('uploader', ['csrf', 'translator'], function(csrf, translator) {
 	};
 
 	module.show = function(data, callback) {
+		var fileSize = data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false;
 		parseModal({
 			showHelp: data.hasOwnProperty('showHelp') && data.showHelp !== undefined ? data.showHelp : true,
-			fileSize: data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false
+			fileSize: fileSize,
+			title: data.title || '[[global:upload_file]]',
+			description: data.description || '',
+			button: data.button || '[[global:upload]]',
+			accept: data.accept ? data.accept.replace(/,/g, '&#44;') : ''
 		}, function(uploadModal) {
 			uploadModal = $(uploadModal);
 
@@ -31,65 +36,71 @@ define('uploader', ['csrf', 'translator'], function(csrf, translator) {
 			uploadForm.attr('action', data.route);
 			uploadForm.find('#params').val(JSON.stringify(data.params));
 
-			uploadModal.find('#pictureUploadSubmitBtn').off('click').on('click', function() {
+			uploadModal.find('#fileUploadSubmitBtn').on('click', function() {
 				uploadForm.submit();
 			});
 
-			uploadForm.off('submit').submit(function() {
+			uploadForm.submit(function() {
+				onSubmit(uploadModal, fileSize, callback);
+				return false;
+			});
+		});
+	};
 
-				function showAlert(type, message) {
-					module.hideAlerts(uploadModal);
-					uploadModal.find('#alert-' + type).translateText(message).removeClass('hide');
-				}
+	module.hideAlerts = function(modal) {
+		$(modal).find('#alert-status, #alert-success, #alert-error, #upload-progress-box').addClass('hide');
+	};
+
+	function onSubmit(uploadModal, fileSize, callback) {
+		function showAlert(type, message) {
+			module.hideAlerts(uploadModal);
+			uploadModal.find('#alert-' + type).translateText(message).removeClass('hide');
+		}
 
-				showAlert('status', '[[uploads:uploading-file]]');
+		showAlert('status', '[[uploads:uploading-file]]');
 
-				uploadModal.find('#upload-progress-bar').css('width', '0%');
-				uploadModal.find('#upload-progress-box').show().removeClass('hide');
+		uploadModal.find('#upload-progress-bar').css('width', '0%');
+		uploadModal.find('#upload-progress-box').show().removeClass('hide');
 
-				if (!uploadModal.find('#userPhotoInput').val()) {
-					showAlert('error', '[[uploads:select-file-to-upload]]');
-					return false;
+		var fileInput = uploadModal.find('#fileInput');
+		if (!fileInput.val()) {
+			return showAlert('error', '[[uploads:select-file-to-upload]]');
+		}
+		if (!hasValidFileSize(fileInput[0], fileSize)) {
+			return showAlert('error', '[[error:file-too-big, ' + fileSize + ']]');
+		}
+
+		uploadModal.find('#uploadForm').ajaxSubmit({
+			headers: {
+				'x-csrf-token': csrf.get()
+			},
+			error: function(xhr) {
+				xhr = maybeParse(xhr);
+				showAlert('error', xhr.responseJSON ? (xhr.responseJSON.error || xhr.statusText) : 'error uploading, code : ' + xhr.status);
+			},
+			uploadProgress: function(event, position, total, percent) {
+				uploadModal.find('#upload-progress-bar').css('width', percent + '%');
+			},
+			success: function(response) {
+				response = maybeParse(response);
+
+				if (response.error) {
+					return showAlert('error', response.error);
 				}
 
-				$(this).ajaxSubmit({
-					headers: {
-						'x-csrf-token': csrf.get()
-					},
-					error: function(xhr) {
-						xhr = maybeParse(xhr);
-						showAlert('error', xhr.responseJSON ? (xhr.responseJSON.error || xhr.statusText) : 'error uploading, code : ' + xhr.status);
-					},
-
-					uploadProgress: function(event, position, total, percent) {
-						uploadModal.find('#upload-progress-bar').css('width', percent + '%');
-					},
-
-					success: function(response) {
-						response = maybeParse(response);
-
-						if (response.error) {
-							showAlert('error', response.error);
-							return;
-						}
-
-						callback(response[0].url);
-
-						showAlert('success', '[[uploads:upload-success]]');
-						setTimeout(function() {
-							module.hideAlerts(uploadModal);
-							uploadModal.modal('hide');
-						}, 750);
-					}
-				});
+				callback(response[0].url);
 
-				return false;
-			});
+				showAlert('success', '[[uploads:upload-success]]');
+				setTimeout(function() {
+					module.hideAlerts(uploadModal);
+					uploadModal.modal('hide');
+				}, 750);
+			}
 		});
-	};
+	}
 
 	function parseModal(tplVals, callback) {
-		templates.parse('partials/modals/upload_picture_modal', tplVals, function(html) {
+		templates.parse('partials/modals/upload_file_modal', tplVals, function(html) {
 			translator.translate(html, callback);
 		});
 	}
@@ -105,9 +116,12 @@ define('uploader', ['csrf', 'translator'], function(csrf, translator) {
 		return response;
 	}
 
-	module.hideAlerts = function(modal) {
-		$(modal).find('#alert-status, #alert-success, #alert-error, #upload-progress-box').addClass('hide');
-	};
+	function hasValidFileSize(fileElement, maxSize) {
+		if (window.FileReader && maxSize) {
+			return fileElement.files[0].size <= maxSize * 1000;
+		}
+		return true;
+	}
 
 	return module;
 });
diff --git a/public/src/utils.js b/public/src/utils.js
index 2c821c1701..78e0013c25 100644
--- a/public/src/utils.js
+++ b/public/src/utils.js
@@ -92,6 +92,23 @@
 			return str;
 		},
 
+		cleanUpTag: function(tag, maxLength) {
+			if (typeof tag !== 'string' || !tag.length ) {
+				return '';
+			}
+
+			tag = tag.trim().toLowerCase();
+			// see https://github.com/NodeBB/NodeBB/issues/4378
+			tag = tag.replace(/\u202E/gi, '');
+			tag = tag.replace(/[,\/#!$%\^\*;:{}=_`<>'"~()?\|]/g, '');
+			tag = tag.substr(0, maxLength || 15).trim();
+			var matches = tag.match(/^[.-]*(.+?)[.-]*$/);
+			if (matches && matches.length > 1) {
+				tag = matches[1];
+			}
+			return tag;
+		},
+
 		removePunctuation: function(str) {
 			return str.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`<>'"~()?]/g, '');
 		},
@@ -292,6 +309,23 @@
 			return labels;
 		},
 
+		/* Retrieved from http://stackoverflow.com/a/7557433 @ 27 Mar 2016 */
+		isElementInViewport: function(el) {
+			//special bonus for those using jQuery
+			if (typeof jQuery === "function" && el instanceof jQuery) {
+				el = el[0];
+			}
+
+			var rect = el.getBoundingClientRect();
+
+			return (
+				rect.top >= 0 &&
+				rect.left >= 0 &&
+				rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
+				rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
+			);
+		},
+
 		// get all the url params in a single key/value hash
 		params: function(options) {
 			var a, hash = {}, params;
diff --git a/public/vendor/autosize.js b/public/vendor/autosize.js
index b6a5c39af8..aa51c70572 100644
--- a/public/vendor/autosize.js
+++ b/public/vendor/autosize.js
@@ -1,155 +1,254 @@
 /*!
-	Autosize 2.0.0
+	Autosize 3.0.15
 	license: MIT
 	http://www.jacklmoore.com/autosize
 */
-'use strict';
-/*globals define*/
-(function (root, factory) {
+(function (global, factory) {
 	if (typeof define === 'function' && define.amd) {
-		// AMD. Register as an anonymous module.
-		define('autosize', factory);
-	} else if (typeof exports === 'object') {
-		// Node. Does not work with strict CommonJS, but
-		// only CommonJS-like environments that support module.exports,
-		// like Node.
-		module.exports = factory();
+		define('autosize', ['exports', 'module'], factory);
+	} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
+		factory(exports, module);
 	} else {
-		// Browser globals (root is window)
-		root.autosize = factory();
-  }
-}(this, function () {
-	function main(ta) {
-		if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || ta.hasAttribute('data-autosize-on')) { return; }
+		var mod = {
+			exports: {}
+		};
+		factory(mod.exports, mod);
+		global.autosize = mod.exports;
+	}
+})(this, function (exports, module) {
+	'use strict';
+
+	var set = typeof Set === 'function' ? new Set() : (function () {
+		var list = [];
+
+		return {
+			has: function has(key) {
+				return Boolean(list.indexOf(key) > -1);
+			},
+			add: function add(key) {
+				list.push(key);
+			},
+			'delete': function _delete(key) {
+				list.splice(list.indexOf(key), 1);
+			} };
+	})();
+
+	var createEvent = function createEvent(name) {
+		return new Event(name);
+	};
+	try {
+		new Event('test');
+	} catch (e) {
+		// IE does not support `new Event()`
+		createEvent = function (name) {
+			var evt = document.createEvent('Event');
+			evt.initEvent(name, true, false);
+			return evt;
+		};
+	}
 
-		var maxHeight;
-		var heightOffset;
-		var amountOfCR;
+	function assign(ta) {
+		var _ref = arguments[1] === undefined ? {} : arguments[1];
+
+		var _ref$setOverflowX = _ref.setOverflowX;
+		var setOverflowX = _ref$setOverflowX === undefined ? true : _ref$setOverflowX;
+		var _ref$setOverflowY = _ref.setOverflowY;
+		var setOverflowY = _ref$setOverflowY === undefined ? true : _ref$setOverflowY;
+
+		if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || set.has(ta)) return;
+
+		var heightOffset = null;
+		var overflowY = null;
+		var clientWidth = ta.clientWidth;
 
 		function init() {
 			var style = window.getComputedStyle(ta, null);
 
+			overflowY = style.overflowY;
+
 			if (style.resize === 'vertical') {
 				ta.style.resize = 'none';
 			} else if (style.resize === 'both') {
 				ta.style.resize = 'horizontal';
 			}
 
-			// horizontal overflow is hidden, so break-word is necessary for handling words longer than the textarea width
-			ta.style.wordWrap = 'break-word';
-
-			// Chrome/Safari-specific fix:
-			// When the textarea y-overflow is hidden, Chrome/Safari doesn't reflow the text to account for the space
-			// made available by removing the scrollbar. This workaround will cause the text to reflow.
-			var width = ta.style.width;
-			ta.style.width = '0px';
-			// Force reflow:
-			/* jshint ignore:start */
-			ta.offsetWidth;
-			/* jshint ignore:end */
-			ta.style.width = width;
-
-			maxHeight = style.maxHeight !== 'none' ? parseFloat(style.maxHeight) : false;
-			
 			if (style.boxSizing === 'content-box') {
-				heightOffset = -(parseFloat(style.paddingTop)+parseFloat(style.paddingBottom));
+				heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
 			} else {
-				heightOffset = parseFloat(style.borderTopWidth)+parseFloat(style.borderBottomWidth);
+				heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
+			}
+			// Fix when a textarea is not on document body and heightOffset is Not a Number
+			if (isNaN(heightOffset)) {
+				heightOffset = 0;
 			}
 
-			amountOfCR = (ta.value.match(/\n/g) || []).length;
-			adjust();
+			update();
 		}
 
-		function adjust() {
-			var startHeight = ta.style.height;
-			var htmlTop = document.documentElement.scrollTop;
-			var bodyTop = document.body.scrollTop;
+		function changeOverflow(value) {
+			{
+				// Chrome/Safari-specific fix:
+				// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
+				// made available by removing the scrollbar. The following forces the necessary text reflow.
+				var width = ta.style.width;
+				ta.style.width = '0px';
+				// Force reflow:
+				/* jshint ignore:start */
+				ta.offsetWidth;
+				/* jshint ignore:end */
+				ta.style.width = width;
+			}
 
-			var newAmountOfCR = (ta.value.match(/\n/g) || []).length;
-			if (newAmountOfCR === amountOfCR) {
-				return;
+			overflowY = value;
+
+			if (setOverflowY) {
+				ta.style.overflowY = value;
 			}
 
-			amountOfCR = newAmountOfCR;
-			
+			resize();
+		}
+
+		function resize() {
+			var htmlTop = window.pageYOffset;
+			var bodyTop = document.body.scrollTop;
+			var originalHeight = ta.style.height;
+
 			ta.style.height = 'auto';
 
-			var endHeight = ta.scrollHeight+heightOffset;
+			var endHeight = ta.scrollHeight + heightOffset;
 
-			if (maxHeight !== false && maxHeight < endHeight) {
-				endHeight = maxHeight;
-				if (ta.style.overflowY !== 'scroll') {
-					ta.style.overflowY = 'scroll';
-				}
-			} else if (ta.style.overflowY !== 'hidden') {
-				ta.style.overflowY = 'hidden';
+			if (ta.scrollHeight === 0) {
+				// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
+				ta.style.height = originalHeight;
+				return;
 			}
 
-			ta.style.height = endHeight+'px';
+			ta.style.height = endHeight + 'px';
+
+			// used to check if an update is actually necessary on window.resize
+			clientWidth = ta.clientWidth;
 
 			// prevents scroll-position jumping
 			document.documentElement.scrollTop = htmlTop;
 			document.body.scrollTop = bodyTop;
+		}
+
+		function update() {
+			var startHeight = ta.style.height;
+
+			resize();
+
+			var style = window.getComputedStyle(ta, null);
+
+			if (style.height !== ta.style.height) {
+				if (overflowY !== 'visible') {
+					changeOverflow('visible');
+				}
+			} else {
+				if (overflowY !== 'hidden') {
+					changeOverflow('hidden');
+				}
+			}
 
 			if (startHeight !== ta.style.height) {
-				var evt = document.createEvent('Event');
-				evt.initEvent('autosize.resized', true, false);
+				var evt = createEvent('autosize:resized');
 				ta.dispatchEvent(evt);
 			}
 		}
 
+		var pageResize = function pageResize() {
+			if (ta.clientWidth !== clientWidth) {
+				update();
+			}
+		};
+
+		var destroy = (function (style) {
+			window.removeEventListener('resize', pageResize, false);
+			ta.removeEventListener('input', update, false);
+			ta.removeEventListener('keyup', update, false);
+			ta.removeEventListener('autosize:destroy', destroy, false);
+			ta.removeEventListener('autosize:update', update, false);
+			set['delete'](ta);
+
+			Object.keys(style).forEach(function (key) {
+				ta.style[key] = style[key];
+			});
+		}).bind(ta, {
+			height: ta.style.height,
+			resize: ta.style.resize,
+			overflowY: ta.style.overflowY,
+			overflowX: ta.style.overflowX,
+			wordWrap: ta.style.wordWrap });
+
+		ta.addEventListener('autosize:destroy', destroy, false);
+
 		// IE9 does not fire onpropertychange or oninput for deletions,
 		// so binding to onkeyup to catch most of those events.
 		// There is no way that I know of to detect something like 'cut' in IE9.
 		if ('onpropertychange' in ta && 'oninput' in ta) {
-			ta.addEventListener('keyup', adjust);
+			ta.addEventListener('keyup', update, false);
 		}
 
-		window.addEventListener('resize', adjust);
-		ta.addEventListener('input', adjust);
-
-		ta.addEventListener('autosize.update', adjust);
+		window.addEventListener('resize', pageResize, false);
+		ta.addEventListener('input', update, false);
+		ta.addEventListener('autosize:update', update, false);
+		set.add(ta);
 
-		ta.addEventListener('autosize.destroy', function(style){
-			window.removeEventListener('resize', adjust);
-			ta.removeEventListener('input', adjust);
-			ta.removeEventListener('keyup', adjust);
-			ta.removeEventListener('autosize.destroy');
-
-			Object.keys(style).forEach(function(key){
-				ta.style[key] = style[key];
-			});
+		if (setOverflowX) {
+			ta.style.overflowX = 'hidden';
+			ta.style.wordWrap = 'break-word';
+		}
 
-			ta.removeAttribute('data-autosize-on');
-		}.bind(ta, {
-			height: ta.style.height,
-			overflow: ta.style.overflow,
-			overflowY: ta.style.overflowY,
-			wordWrap: ta.style.wordWrap,
-			resize: ta.style.resize
-		}));
+		init();
+	}
 
-		ta.setAttribute('data-autosize-on', true);
-		ta.style.overflow = 'hidden';
-		ta.style.overflowY = 'hidden';
+	function destroy(ta) {
+		if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
+		var evt = createEvent('autosize:destroy');
+		ta.dispatchEvent(evt);
+	}
 
-		init();		
+	function update(ta) {
+		if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
+		var evt = createEvent('autosize:update');
+		ta.dispatchEvent(evt);
 	}
 
-	// Do nothing in IE8 or lower
-	if (typeof window.getComputedStyle !== 'function') {
-		return function(elements) {
-			return elements;
+	var autosize = null;
+
+	// Do nothing in Node.js environment and IE8 (or lower)
+	if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
+		autosize = function (el) {
+			return el;
+		};
+		autosize.destroy = function (el) {
+			return el;
+		};
+		autosize.update = function (el) {
+			return el;
 		};
 	} else {
-		return function(elements) {
-			if (elements && elements.length) {
-				Array.prototype.forEach.call(elements, main);
-			} else if (elements && elements.nodeName) {
-				main(elements);
+		autosize = function (el, options) {
+			if (el) {
+				Array.prototype.forEach.call(el.length ? el : [el], function (x) {
+					return assign(x, options);
+				});
+			}
+			return el;
+		};
+		autosize.destroy = function (el) {
+			if (el) {
+				Array.prototype.forEach.call(el.length ? el : [el], destroy);
 			}
-			return elements;
+			return el;
+		};
+		autosize.update = function (el) {
+			if (el) {
+				Array.prototype.forEach.call(el.length ? el : [el], update);
+			}
+			return el;
 		};
 	}
-}));
\ No newline at end of file
+
+	module.exports = autosize;
+});
\ No newline at end of file
diff --git a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css
index 55f7c09df0..f08e955b17 100644
--- a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css
+++ b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css
@@ -1,10 +1,14 @@
+/*
+ * bootstrap-tagsinput v0.8.0
+ *
+ */
+
 .bootstrap-tagsinput {
   background-color: #fff;
   border: 1px solid #ccc;
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
   display: inline-block;
   padding: 4px 6px;
-  margin-bottom: 10px;
   color: #555;
   vertical-align: middle;
   border-radius: 4px;
@@ -17,11 +21,21 @@
   box-shadow: none;
   outline: none;
   background-color: transparent;
-  padding: 0;
+  padding: 0 6px;
   margin: 0;
-  width: auto !important;
+  width: auto;
   max-width: inherit;
 }
+.bootstrap-tagsinput.form-control input::-moz-placeholder {
+  color: #777;
+  opacity: 1;
+}
+.bootstrap-tagsinput.form-control input:-ms-input-placeholder {
+  color: #777;
+}
+.bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
+  color: #777;
+}
 .bootstrap-tagsinput input:focus {
   border: none;
   box-shadow: none;
@@ -43,4 +57,4 @@
 }
 .bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
   box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-}
+}
\ No newline at end of file
diff --git a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js
index 4cbe2ae178..49db4041a6 100644
--- a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js
+++ b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js
@@ -1,6 +1,6 @@
 /*
- * bootstrap-tagsinput v0.7.1 by Tim Schlechter
+ * bootstrap-tagsinput v0.8.0
  *
  */
 
-!function(a){"use strict";function b(b,c){this.isInit=!0,this.itemsArray=[],this.$element=a(b),this.$element.hide(),this.isSelect="SELECT"===b.tagName,this.multiple=this.isSelect&&b.hasAttribute("multiple"),this.objectItems=c&&c.itemValue,this.placeholderText=b.hasAttribute("placeholder")?this.$element.attr("placeholder"):"",this.inputSize=Math.max(1,this.placeholderText.length),this.$container=a('<div class="bootstrap-tagsinput"></div>'),this.$input=a('<input type="text" placeholder="'+this.placeholderText+'"/>').appendTo(this.$container),this.$element.before(this.$container),this.build(c),this.isInit=!1}function c(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(a){return a[c]}}}function d(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(){return c}}}function e(a){return a?i.text(a).html():""}function f(a){var b=0;if(document.selection){a.focus();var c=document.selection.createRange();c.moveStart("character",-a.value.length),b=c.text.length}else(a.selectionStart||"0"==a.selectionStart)&&(b=a.selectionStart);return b}function g(b,c){var d=!1;return a.each(c,function(a,c){if("number"==typeof c&&b.which===c)return d=!0,!1;if(b.which===c.which){var e=!c.hasOwnProperty("altKey")||b.altKey===c.altKey,f=!c.hasOwnProperty("shiftKey")||b.shiftKey===c.shiftKey,g=!c.hasOwnProperty("ctrlKey")||b.ctrlKey===c.ctrlKey;if(e&&f&&g)return d=!0,!1}}),d}var h={tagClass:function(a){return"label label-info"},focusClass:"focus",itemValue:function(a){return a?a.toString():a},itemText:function(a){return this.itemValue(a)},itemTitle:function(a){return null},freeInput:!0,addOnBlur:!0,maxTags:void 0,maxChars:void 0,confirmKeys:[13,44],delimiter:",",delimiterRegex:null,cancelConfirmKeysOnEmpty:!1,onTagExists:function(a,b){b.hide().fadeIn()},trimValue:!1,allowDuplicates:!1};b.prototype={constructor:b,add:function(b,c,d){var f=this;if(!(f.options.maxTags&&f.itemsArray.length>=f.options.maxTags)&&(b===!1||b)){if("string"==typeof b&&f.options.trimValue&&(b=a.trim(b)),"object"==typeof b&&!f.objectItems)throw"Can't add objects when itemValue option is not set";if(!b.toString().match(/^\s*$/)){if(f.isSelect&&!f.multiple&&f.itemsArray.length>0&&f.remove(f.itemsArray[0]),"string"==typeof b&&"INPUT"===this.$element[0].tagName){var g=f.options.delimiterRegex?f.options.delimiterRegex:f.options.delimiter,h=b.split(g);if(h.length>1){for(var i=0;i<h.length;i++)this.add(h[i],!0);return void(c||f.pushVal())}}var j=f.options.itemValue(b),k=f.options.itemText(b),l=f.options.tagClass(b),m=f.options.itemTitle(b),n=a.grep(f.itemsArray,function(a){return f.options.itemValue(a)===j})[0];if(!n||f.options.allowDuplicates){if(!(f.items().toString().length+b.length+1>f.options.maxInputLength)){var o=a.Event("beforeItemAdd",{item:b,cancel:!1,options:d});if(f.$element.trigger(o),!o.cancel){f.itemsArray.push(b);var p=a('<span class="tag '+e(l)+(null!==m?'" title="'+m:"")+'">'+e(k)+'<span data-role="remove"></span></span>');p.data("item",b),f.findInputWrapper().before(p),p.after(" ");var q=a('option[value="'+encodeURIComponent(j)+'"]',f.$element).length||a('option[value="'+e(j)+'"]',f.$element).length;if(f.isSelect&&!q){var r=a("<option selected>"+e(k)+"</option>");r.data("item",b),r.attr("value",j),f.$element.append(r)}c||f.pushVal(),(f.options.maxTags===f.itemsArray.length||f.items().toString().length===f.options.maxInputLength)&&f.$container.addClass("bootstrap-tagsinput-max"),a(".typeahead, .twitter-typeahead",f.$container).length&&f.$input.typeahead("val",""),this.isInit?f.$element.trigger(a.Event("itemAddedOnInit",{item:b,options:d})):f.$element.trigger(a.Event("itemAdded",{item:b,options:d}))}}}else if(f.options.onTagExists){var s=a(".tag",f.$container).filter(function(){return a(this).data("item")===n});f.options.onTagExists(b,s)}}}},remove:function(b,c,d){var e=this;if(e.objectItems&&(b="object"==typeof b?a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==e.options.itemValue(b)}):a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==b}),b=b[b.length-1]),b){var f=a.Event("beforeItemRemove",{item:b,cancel:!1,options:d});if(e.$element.trigger(f),f.cancel)return;a(".tag",e.$container).filter(function(){return a(this).data("item")===b}).remove(),a("option",e.$element).filter(function(){return a(this).data("item")===b}).remove(),-1!==a.inArray(b,e.itemsArray)&&e.itemsArray.splice(a.inArray(b,e.itemsArray),1)}c||e.pushVal(),e.options.maxTags>e.itemsArray.length&&e.$container.removeClass("bootstrap-tagsinput-max"),e.$element.trigger(a.Event("itemRemoved",{item:b,options:d}))},removeAll:function(){var b=this;for(a(".tag",b.$container).remove(),a("option",b.$element).remove();b.itemsArray.length>0;)b.itemsArray.pop();b.pushVal()},refresh:function(){var b=this;a(".tag",b.$container).each(function(){var c=a(this),d=c.data("item"),f=b.options.itemValue(d),g=b.options.itemText(d),h=b.options.tagClass(d);if(c.attr("class",null),c.addClass("tag "+e(h)),c.contents().filter(function(){return 3==this.nodeType})[0].nodeValue=e(g),b.isSelect){var i=a("option",b.$element).filter(function(){return a(this).data("item")===d});i.attr("value",f)}})},items:function(){return this.itemsArray},pushVal:function(){var b=this,c=a.map(b.items(),function(a){return b.options.itemValue(a).toString()});b.$element.val(c,!0).trigger("change")},build:function(b){var e=this;if(e.options=a.extend({},h,b),e.objectItems&&(e.options.freeInput=!1),c(e.options,"itemValue"),c(e.options,"itemText"),d(e.options,"tagClass"),e.options.typeahead){var i=e.options.typeahead||{};d(i,"source"),e.$input.typeahead(a.extend({},i,{source:function(b,c){function d(a){for(var b=[],d=0;d<a.length;d++){var g=e.options.itemText(a[d]);f[g]=a[d],b.push(g)}c(b)}this.map={};var f=this.map,g=i.source(b);a.isFunction(g.success)?g.success(d):a.isFunction(g.then)?g.then(d):a.when(g).then(d)},updater:function(a){return e.add(this.map[a]),this.map[a]},matcher:function(a){return-1!==a.toLowerCase().indexOf(this.query.trim().toLowerCase())},sorter:function(a){return a.sort()},highlighter:function(a){var b=new RegExp("("+this.query+")","gi");return a.replace(b,"<strong>$1</strong>")}}))}if(e.options.typeaheadjs){var j=null,k={},l=e.options.typeaheadjs;a.isArray(l)?(j=l[0],k=l[1]):k=l,e.$input.typeahead(j,k).on("typeahead:selected",a.proxy(function(a,b){k.valueKey?e.add(b[k.valueKey]):e.add(b),e.$input.typeahead("val","")},e))}e.$container.on("click",a.proxy(function(a){e.$element.attr("disabled")||e.$input.removeAttr("disabled"),e.$input.focus()},e)),e.options.addOnBlur&&e.options.freeInput&&e.$input.on("focusout",a.proxy(function(b){0===a(".typeahead, .twitter-typeahead",e.$container).length&&(e.add(e.$input.val()),e.$input.val(""))},e)),e.$container.on({focusin:function(){e.$container.addClass(e.options.focusClass)},focusout:function(){e.$container.removeClass(e.options.focusClass)}}),e.$container.on("keydown","input",a.proxy(function(b){var c=a(b.target),d=e.findInputWrapper();if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");switch(b.which){case 8:if(0===f(c[0])){var g=d.prev();g.length&&e.remove(g.data("item"))}break;case 46:if(0===f(c[0])){var h=d.next();h.length&&e.remove(h.data("item"))}break;case 37:var i=d.prev();0===c.val().length&&i[0]&&(i.before(d),c.focus());break;case 39:var j=d.next();0===c.val().length&&j[0]&&(j.after(d),c.focus())}var k=c.val().length;Math.ceil(k/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("keypress","input",a.proxy(function(b){var c=a(b.target);if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");var d=c.val(),f=e.options.maxChars&&d.length>=e.options.maxChars;e.options.freeInput&&(g(b,e.options.confirmKeys)||f)&&(0!==d.length&&(e.add(f?d.substr(0,e.options.maxChars):d),c.val("")),e.options.cancelConfirmKeysOnEmpty===!1&&b.preventDefault());var h=c.val().length;Math.ceil(h/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("click","[data-role=remove]",a.proxy(function(b){e.$element.attr("disabled")||e.remove(a(b.target).closest(".tag").data("item"))},e)),e.options.itemValue===h.itemValue&&("INPUT"===e.$element[0].tagName?e.add(e.$element.val()):a("option",e.$element).each(function(){e.add(a(this).attr("value"),!0)}))},destroy:function(){var a=this;a.$container.off("keypress","input"),a.$container.off("click","[role=remove]"),a.$container.remove(),a.$element.removeData("tagsinput"),a.$element.show()},focus:function(){this.$input.focus()},input:function(){return this.$input},findInputWrapper:function(){for(var b=this.$input[0],c=this.$container[0];b&&b.parentNode!==c;)b=b.parentNode;return a(b)}},a.fn.tagsinput=function(c,d,e){var f=[];return this.each(function(){var g=a(this).data("tagsinput");if(g)if(c||d){if(void 0!==g[c]){if(3===g[c].length&&void 0!==e)var h=g[c](d,null,e);else var h=g[c](d);void 0!==h&&f.push(h)}}else f.push(g);else g=new b(this,c),a(this).data("tagsinput",g),f.push(g),"SELECT"===this.tagName&&a("option",a(this)).attr("selected","selected"),a(this).val(a(this).val())}),"string"==typeof c?f.length>1?f:f[0]:f},a.fn.tagsinput.Constructor=b;var i=a("<div />");a(function(){a("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput()})}(window.jQuery);
+!function(a){"use strict";function b(b,c){this.isInit=!0,this.itemsArray=[],this.$element=a(b),this.$element.hide(),this.isSelect="SELECT"===b.tagName,this.multiple=this.isSelect&&b.hasAttribute("multiple"),this.objectItems=c&&c.itemValue,this.placeholderText=b.hasAttribute("placeholder")?this.$element.attr("placeholder"):"",this.inputSize=Math.max(1,this.placeholderText.length),this.$container=a('<div class="bootstrap-tagsinput"></div>'),this.$input=a('<input type="text" placeholder="'+this.placeholderText+'"/>').appendTo(this.$container),this.$element.before(this.$container),this.build(c),this.isInit=!1}function c(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(a){return a[c]}}}function d(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(){return c}}}function e(a){return a?i.text(a).html():""}function f(a){var b=0;if(document.selection){a.focus();var c=document.selection.createRange();c.moveStart("character",-a.value.length),b=c.text.length}else(a.selectionStart||"0"==a.selectionStart)&&(b=a.selectionStart);return b}function g(b,c){var d=!1;return a.each(c,function(a,c){if("number"==typeof c&&b.which===c)return d=!0,!1;if(b.which===c.which){var e=!c.hasOwnProperty("altKey")||b.altKey===c.altKey,f=!c.hasOwnProperty("shiftKey")||b.shiftKey===c.shiftKey,g=!c.hasOwnProperty("ctrlKey")||b.ctrlKey===c.ctrlKey;if(e&&f&&g)return d=!0,!1}}),d}var h={tagClass:function(a){return"label label-info"},focusClass:"focus",itemValue:function(a){return a?a.toString():a},itemText:function(a){return this.itemValue(a)},itemTitle:function(a){return null},freeInput:!0,addOnBlur:!0,maxTags:void 0,maxChars:void 0,confirmKeys:[13,44],delimiter:",",delimiterRegex:null,cancelConfirmKeysOnEmpty:!1,onTagExists:function(a,b){b.hide().fadeIn()},trimValue:!1,allowDuplicates:!1,triggerChange:!0};b.prototype={constructor:b,add:function(b,c,d){var f=this;if(!(f.options.maxTags&&f.itemsArray.length>=f.options.maxTags)&&(b===!1||b)){if("string"==typeof b&&f.options.trimValue&&(b=a.trim(b)),"object"==typeof b&&!f.objectItems)throw"Can't add objects when itemValue option is not set";if(!b.toString().match(/^\s*$/)){if(f.isSelect&&!f.multiple&&f.itemsArray.length>0&&f.remove(f.itemsArray[0]),"string"==typeof b&&"INPUT"===this.$element[0].tagName){var g=f.options.delimiterRegex?f.options.delimiterRegex:f.options.delimiter,h=b.split(g);if(h.length>1){for(var i=0;i<h.length;i++)this.add(h[i],!0);return void(c||f.pushVal(f.options.triggerChange))}}var j=f.options.itemValue(b),k=f.options.itemText(b),l=f.options.tagClass(b),m=f.options.itemTitle(b),n=a.grep(f.itemsArray,function(a){return f.options.itemValue(a)===j})[0];if(!n||f.options.allowDuplicates){if(!(f.items().toString().length+b.length+1>f.options.maxInputLength)){var o=a.Event("beforeItemAdd",{item:b,cancel:!1,options:d});if(f.$element.trigger(o),!o.cancel){f.itemsArray.push(b);var p=a('<span class="tag '+e(l)+(null!==m?'" title="'+m:"")+'">'+e(k)+'<span data-role="remove"></span></span>');p.data("item",b),f.findInputWrapper().before(p),p.after(" ");var q=a('option[value="'+encodeURIComponent(j)+'"]',f.$element).length||a('option[value="'+e(j)+'"]',f.$element).length;if(f.isSelect&&!q){var r=a("<option selected>"+e(k)+"</option>");r.data("item",b),r.attr("value",j),f.$element.append(r)}c||f.pushVal(f.options.triggerChange),(f.options.maxTags===f.itemsArray.length||f.items().toString().length===f.options.maxInputLength)&&f.$container.addClass("bootstrap-tagsinput-max"),a(".typeahead, .twitter-typeahead",f.$container).length&&f.$input.typeahead("val",""),this.isInit?f.$element.trigger(a.Event("itemAddedOnInit",{item:b,options:d})):f.$element.trigger(a.Event("itemAdded",{item:b,options:d}))}}}else if(f.options.onTagExists){var s=a(".tag",f.$container).filter(function(){return a(this).data("item")===n});f.options.onTagExists(b,s)}}}},remove:function(b,c,d){var e=this;if(e.objectItems&&(b="object"==typeof b?a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==e.options.itemValue(b)}):a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==b}),b=b[b.length-1]),b){var f=a.Event("beforeItemRemove",{item:b,cancel:!1,options:d});if(e.$element.trigger(f),f.cancel)return;a(".tag",e.$container).filter(function(){return a(this).data("item")===b}).remove(),a("option",e.$element).filter(function(){return a(this).data("item")===b}).remove(),-1!==a.inArray(b,e.itemsArray)&&e.itemsArray.splice(a.inArray(b,e.itemsArray),1)}c||e.pushVal(e.options.triggerChange),e.options.maxTags>e.itemsArray.length&&e.$container.removeClass("bootstrap-tagsinput-max"),e.$element.trigger(a.Event("itemRemoved",{item:b,options:d}))},removeAll:function(){var b=this;for(a(".tag",b.$container).remove(),a("option",b.$element).remove();b.itemsArray.length>0;)b.itemsArray.pop();b.pushVal(b.options.triggerChange)},refresh:function(){var b=this;a(".tag",b.$container).each(function(){var c=a(this),d=c.data("item"),f=b.options.itemValue(d),g=b.options.itemText(d),h=b.options.tagClass(d);if(c.attr("class",null),c.addClass("tag "+e(h)),c.contents().filter(function(){return 3==this.nodeType})[0].nodeValue=e(g),b.isSelect){var i=a("option",b.$element).filter(function(){return a(this).data("item")===d});i.attr("value",f)}})},items:function(){return this.itemsArray},pushVal:function(){var b=this,c=a.map(b.items(),function(a){return b.options.itemValue(a).toString()});b.$element.val(c,!0),b.options.triggerChange&&b.$element.trigger("change")},build:function(b){var e=this;if(e.options=a.extend({},h,b),e.objectItems&&(e.options.freeInput=!1),c(e.options,"itemValue"),c(e.options,"itemText"),d(e.options,"tagClass"),e.options.typeahead){var i=e.options.typeahead||{};d(i,"source"),e.$input.typeahead(a.extend({},i,{source:function(b,c){function d(a){for(var b=[],d=0;d<a.length;d++){var g=e.options.itemText(a[d]);f[g]=a[d],b.push(g)}c(b)}this.map={};var f=this.map,g=i.source(b);a.isFunction(g.success)?g.success(d):a.isFunction(g.then)?g.then(d):a.when(g).then(d)},updater:function(a){return e.add(this.map[a]),this.map[a]},matcher:function(a){return-1!==a.toLowerCase().indexOf(this.query.trim().toLowerCase())},sorter:function(a){return a.sort()},highlighter:function(a){var b=new RegExp("("+this.query+")","gi");return a.replace(b,"<strong>$1</strong>")}}))}if(e.options.typeaheadjs){var j=null,k={},l=e.options.typeaheadjs;a.isArray(l)?(j=l[0],k=l[1]):k=l,e.$input.typeahead(j,k).on("typeahead:selected",a.proxy(function(a,b){k.valueKey?e.add(b[k.valueKey]):e.add(b),e.$input.typeahead("val","")},e))}e.$container.on("click",a.proxy(function(a){e.$element.attr("disabled")||e.$input.removeAttr("disabled"),e.$input.focus()},e)),e.options.addOnBlur&&e.options.freeInput&&e.$input.on("focusout",a.proxy(function(b){0===a(".typeahead, .twitter-typeahead",e.$container).length&&(e.add(e.$input.val()),e.$input.val(""))},e)),e.$container.on({focusin:function(){e.$container.addClass(e.options.focusClass)},focusout:function(){e.$container.removeClass(e.options.focusClass)}}),e.$container.on("keydown","input",a.proxy(function(b){var c=a(b.target),d=e.findInputWrapper();if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");switch(b.which){case 8:if(0===f(c[0])){var g=d.prev();g.length&&e.remove(g.data("item"))}break;case 46:if(0===f(c[0])){var h=d.next();h.length&&e.remove(h.data("item"))}break;case 37:var i=d.prev();0===c.val().length&&i[0]&&(i.before(d),c.focus());break;case 39:var j=d.next();0===c.val().length&&j[0]&&(j.after(d),c.focus())}var k=c.val().length;Math.ceil(k/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("keypress","input",a.proxy(function(b){var c=a(b.target);if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");var d=c.val(),f=e.options.maxChars&&d.length>=e.options.maxChars;e.options.freeInput&&(g(b,e.options.confirmKeys)||f)&&(0!==d.length&&(e.add(f?d.substr(0,e.options.maxChars):d),c.val("")),e.options.cancelConfirmKeysOnEmpty===!1&&b.preventDefault());var h=c.val().length;Math.ceil(h/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("click","[data-role=remove]",a.proxy(function(b){e.$element.attr("disabled")||e.remove(a(b.target).closest(".tag").data("item"))},e)),e.options.itemValue===h.itemValue&&("INPUT"===e.$element[0].tagName?e.add(e.$element.val()):a("option",e.$element).each(function(){e.add(a(this).attr("value"),!0)}))},destroy:function(){var a=this;a.$container.off("keypress","input"),a.$container.off("click","[role=remove]"),a.$container.remove(),a.$element.removeData("tagsinput"),a.$element.show()},focus:function(){this.$input.focus()},input:function(){return this.$input},findInputWrapper:function(){for(var b=this.$input[0],c=this.$container[0];b&&b.parentNode!==c;)b=b.parentNode;return a(b)}},a.fn.tagsinput=function(c,d,e){var f=[];return this.each(function(){var g=a(this).data("tagsinput");if(g)if(c||d){if(void 0!==g[c]){if(3===g[c].length&&void 0!==e)var h=g[c](d,null,e);else var h=g[c](d);void 0!==h&&f.push(h)}}else f.push(g);else g=new b(this,c),a(this).data("tagsinput",g),f.push(g),"SELECT"===this.tagName&&a("option",a(this)).attr("selected","selected"),a(this).val(a(this).val())}),"string"==typeof c?f.length>1?f:f[0]:f},a.fn.tagsinput.Constructor=b;var i=a("<div />");a(function(){a("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput()})}(window.jQuery);
diff --git a/public/vendor/jquery/textcomplete/jquery.textcomplete.css b/public/vendor/jquery/textcomplete/jquery.textcomplete.css
deleted file mode 100644
index d33f066c5a..0000000000
--- a/public/vendor/jquery/textcomplete/jquery.textcomplete.css
+++ /dev/null
@@ -1,33 +0,0 @@
-/* Sample */
-
-/*.dropdown-menu {
-    border: 1px solid #ddd;
-    background-color: white;
-}
-
-.dropdown-menu li {
-    border-top: 1px solid #ddd;
-    padding: 2px 5px;
-}
-
-.dropdown-menu li:first-child {
-    border-top: none;
-}
-
-.dropdown-menu li:hover,
-.dropdown-menu .active {
-    background-color: rgb(110, 183, 219);
-}*/
-
-
-/* SHOULD not modify */
-
-/*.dropdown-menu {
-    list-style: none;
-    padding: 0;
-    margin: 0;
-}
-
-.dropdown-menu a:hover {
-    cursor: pointer;
-}*/
\ No newline at end of file
diff --git a/public/vendor/jquery/textcomplete/jquery.textcomplete.js b/public/vendor/jquery/textcomplete/jquery.textcomplete.js
new file mode 100644
index 0000000000..ad1d508450
--- /dev/null
+++ b/public/vendor/jquery/textcomplete/jquery.textcomplete.js
@@ -0,0 +1,1381 @@
+(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(['jquery'], factory);
+  } else if (typeof module === "object" && module.exports) {
+    var $ = require('jquery');
+    module.exports = factory($);
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+}(function (jQuery) {
+
+/*!
+ * jQuery.textcomplete
+ *
+ * Repository: https://github.com/yuku-t/jquery-textcomplete
+ * License:    MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE)
+ * Author:     Yuku Takahashi
+ * Version:	   1.3.1
+ */
+
+if (typeof jQuery === 'undefined') {
+  throw new Error('jQuery.textcomplete requires jQuery');
+}
+
++function ($) {
+  'use strict';
+
+  var warn = function (message) {
+    if (console.warn) { console.warn(message); }
+  };
+
+  var id = 1;
+
+  $.fn.textcomplete = function (strategies, option) {
+    var args = Array.prototype.slice.call(arguments);
+    return this.each(function () {
+      var self = this;
+      var $this = $(this);
+      var completer = $this.data('textComplete');
+      if (!completer) {
+        option || (option = {});
+        option._oid = id++;  // unique object id
+        completer = new $.fn.textcomplete.Completer(this, option);
+        $this.data('textComplete', completer);
+      }
+      if (typeof strategies === 'string') {
+        if (!completer) return;
+        args.shift()
+        completer[strategies].apply(completer, args);
+        if (strategies === 'destroy') {
+          $this.removeData('textComplete');
+        }
+      } else {
+        // For backward compatibility.
+        // TODO: Remove at v0.4
+        $.each(strategies, function (obj) {
+          $.each(['header', 'footer', 'placement', 'maxCount'], function (name) {
+            if (obj[name]) {
+              completer.option[name] = obj[name];
+              warn(name + 'as a strategy param is deprecated. Use option.');
+              delete obj[name];
+            }
+          });
+        });
+        completer.register($.fn.textcomplete.Strategy.parse(strategies, {
+          el: self,
+          $el: $this
+        }));
+      }
+    });
+  };
+
+}(jQuery);
+
++function ($) {
+  'use strict';
+
+  // Exclusive execution control utility.
+  //
+  // func - The function to be locked. It is executed with a function named
+  //        `free` as the first argument. Once it is called, additional
+  //        execution are ignored until the free is invoked. Then the last
+  //        ignored execution will be replayed immediately.
+  //
+  // Examples
+  //
+  //   var lockedFunc = lock(function (free) {
+  //     setTimeout(function { free(); }, 1000); // It will be free in 1 sec.
+  //     console.log('Hello, world');
+  //   });
+  //   lockedFunc();  // => 'Hello, world'
+  //   lockedFunc();  // none
+  //   lockedFunc();  // none
+  //   // 1 sec past then
+  //   // => 'Hello, world'
+  //   lockedFunc();  // => 'Hello, world'
+  //   lockedFunc();  // none
+  //
+  // Returns a wrapped function.
+  var lock = function (func) {
+    var locked, queuedArgsToReplay;
+
+    return function () {
+      // Convert arguments into a real array.
+      var args = Array.prototype.slice.call(arguments);
+      if (locked) {
+        // Keep a copy of this argument list to replay later.
+        // OK to overwrite a previous value because we only replay
+        // the last one.
+        queuedArgsToReplay = args;
+        return;
+      }
+      locked = true;
+      var self = this;
+      args.unshift(function replayOrFree() {
+        if (queuedArgsToReplay) {
+          // Other request(s) arrived while we were locked.
+          // Now that the lock is becoming available, replay
+          // the latest such request, then call back here to
+          // unlock (or replay another request that arrived
+          // while this one was in flight).
+          var replayArgs = queuedArgsToReplay;
+          queuedArgsToReplay = undefined;
+          replayArgs.unshift(replayOrFree);
+          func.apply(self, replayArgs);
+        } else {
+          locked = false;
+        }
+      });
+      func.apply(this, args);
+    };
+  };
+
+  var isString = function (obj) {
+    return Object.prototype.toString.call(obj) === '[object String]';
+  };
+
+  var isFunction = function (obj) {
+    return Object.prototype.toString.call(obj) === '[object Function]';
+  };
+
+  var uniqueId = 0;
+
+  function Completer(element, option) {
+    this.$el        = $(element);
+    this.id         = 'textcomplete' + uniqueId++;
+    this.strategies = [];
+    this.views      = [];
+    this.option     = $.extend({}, Completer._getDefaults(), option);
+
+    if (!this.$el.is('input[type=text]') && !this.$el.is('input[type=search]') && !this.$el.is('textarea') && !element.isContentEditable && element.contentEditable != 'true') {
+      throw new Error('textcomplete must be called on a Textarea or a ContentEditable.');
+    }
+
+    if (element === document.activeElement) {
+      // element has already been focused. Initialize view objects immediately.
+      this.initialize()
+    } else {
+      // Initialize view objects lazily.
+      var self = this;
+      this.$el.one('focus.' + this.id, function () { self.initialize(); });
+    }
+  }
+
+  Completer._getDefaults = function () {
+    if (!Completer.DEFAULTS) {
+      Completer.DEFAULTS = {
+        appendTo: $('body'),
+        zIndex: '100'
+      };
+    }
+
+    return Completer.DEFAULTS;
+  }
+
+  $.extend(Completer.prototype, {
+    // Public properties
+    // -----------------
+
+    id:         null,
+    option:     null,
+    strategies: null,
+    adapter:    null,
+    dropdown:   null,
+    $el:        null,
+
+    // Public methods
+    // --------------
+
+    initialize: function () {
+      var element = this.$el.get(0);
+      // Initialize view objects.
+      this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option);
+      var Adapter, viewName;
+      if (this.option.adapter) {
+        Adapter = this.option.adapter;
+      } else {
+        if (this.$el.is('textarea') || this.$el.is('input[type=text]') || this.$el.is('input[type=search]')) {
+          viewName = typeof element.selectionEnd === 'number' ? 'Textarea' : 'IETextarea';
+        } else {
+          viewName = 'ContentEditable';
+        }
+        Adapter = $.fn.textcomplete[viewName];
+      }
+      this.adapter = new Adapter(element, this, this.option);
+    },
+
+    destroy: function () {
+      this.$el.off('.' + this.id);
+      if (this.adapter) {
+        this.adapter.destroy();
+      }
+      if (this.dropdown) {
+        this.dropdown.destroy();
+      }
+      this.$el = this.adapter = this.dropdown = null;
+    },
+
+    deactivate: function () {
+      if (this.dropdown) {
+        this.dropdown.deactivate();
+      }
+    },
+
+    // Invoke textcomplete.
+    trigger: function (text, skipUnchangedTerm) {
+      if (!this.dropdown) { this.initialize(); }
+      text != null || (text = this.adapter.getTextFromHeadToCaret());
+      var searchQuery = this._extractSearchQuery(text);
+      if (searchQuery.length) {
+        var term = searchQuery[1];
+        // Ignore shift-key, ctrl-key and so on.
+        if (skipUnchangedTerm && this._term === term && term !== "") { return; }
+        this._term = term;
+        this._search.apply(this, searchQuery);
+      } else {
+        this._term = null;
+        this.dropdown.deactivate();
+      }
+    },
+
+    fire: function (eventName) {
+      var args = Array.prototype.slice.call(arguments, 1);
+      this.$el.trigger(eventName, args);
+      return this;
+    },
+
+    register: function (strategies) {
+      Array.prototype.push.apply(this.strategies, strategies);
+    },
+
+    // Insert the value into adapter view. It is called when the dropdown is clicked
+    // or selected.
+    //
+    // value    - The selected element of the array callbacked from search func.
+    // strategy - The Strategy object.
+    // e        - Click or keydown event object.
+    select: function (value, strategy, e) {
+      this._term = null;
+      this.adapter.select(value, strategy, e);
+      this.fire('change').fire('textComplete:select', value, strategy);
+      this.adapter.focus();
+    },
+
+    // Private properties
+    // ------------------
+
+    _clearAtNext: true,
+    _term:        null,
+
+    // Private methods
+    // ---------------
+
+    // Parse the given text and extract the first matching strategy.
+    //
+    // Returns an array including the strategy, the query term and the match
+    // object if the text matches an strategy; otherwise returns an empty array.
+    _extractSearchQuery: function (text) {
+      for (var i = 0; i < this.strategies.length; i++) {
+        var strategy = this.strategies[i];
+        var context = strategy.context(text);
+        if (context || context === '') {
+          var matchRegexp = isFunction(strategy.match) ? strategy.match(text) : strategy.match;
+          if (isString(context)) { text = context; }
+          var match = text.match(matchRegexp);
+          if (match) { return [strategy, match[strategy.index], match]; }
+        }
+      }
+      return []
+    },
+
+    // Call the search method of selected strategy..
+    _search: lock(function (free, strategy, term, match) {
+      var self = this;
+      strategy.search(term, function (data, stillSearching) {
+        if (!self.dropdown.shown) {
+          self.dropdown.activate();
+        }
+        if (self._clearAtNext) {
+          // The first callback in the current lock.
+          self.dropdown.clear();
+          self._clearAtNext = false;
+        }
+        self.dropdown.setPosition(self.adapter.getCaretPosition());
+        self.dropdown.render(self._zip(data, strategy, term));
+        if (!stillSearching) {
+          // The last callback in the current lock.
+          free();
+          self._clearAtNext = true; // Call dropdown.clear at the next time.
+        }
+      }, match);
+    }),
+
+    // Build a parameter for Dropdown#render.
+    //
+    // Examples
+    //
+    //  this._zip(['a', 'b'], 's');
+    //  //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }]
+    _zip: function (data, strategy, term) {
+      return $.map(data, function (value) {
+        return { value: value, strategy: strategy, term: term };
+      });
+    }
+  });
+
+  $.fn.textcomplete.Completer = Completer;
+}(jQuery);
+
++function ($) {
+  'use strict';
+
+  var $window = $(window);
+
+  var include = function (zippedData, datum) {
+    var i, elem;
+    var idProperty = datum.strategy.idProperty
+    for (i = 0; i < zippedData.length; i++) {
+      elem = zippedData[i];
+      if (elem.strategy !== datum.strategy) continue;
+      if (idProperty) {
+        if (elem.value[idProperty] === datum.value[idProperty]) return true;
+      } else {
+        if (elem.value === datum.value) return true;
+      }
+    }
+    return false;
+  };
+
+  var dropdownViews = {};
+  $(document).on('click', function (e) {
+    var id = e.originalEvent && e.originalEvent.keepTextCompleteDropdown;
+    $.each(dropdownViews, function (key, view) {
+      if (key !== id) { view.deactivate(); }
+    });
+  });
+
+  var commands = {
+    SKIP_DEFAULT: 0,
+    KEY_UP: 1,
+    KEY_DOWN: 2,
+    KEY_ENTER: 3,
+    KEY_PAGEUP: 4,
+    KEY_PAGEDOWN: 5,
+    KEY_ESCAPE: 6
+  };
+
+  // Dropdown view
+  // =============
+
+  // Construct Dropdown object.
+  //
+  // element - Textarea or contenteditable element.
+  function Dropdown(element, completer, option) {
+    this.$el       = Dropdown.createElement(option);
+    this.completer = completer;
+    this.id        = completer.id + 'dropdown';
+    this._data     = []; // zipped data.
+    this.$inputEl  = $(element);
+    this.option    = option;
+
+    // Override setPosition method.
+    if (option.listPosition) { this.setPosition = option.listPosition; }
+    if (option.height) { this.$el.height(option.height); }
+    var self = this;
+    $.each(['maxCount', 'placement', 'footer', 'header', 'noResultsMessage', 'className'], function (_i, name) {
+      if (option[name] != null) { self[name] = option[name]; }
+    });
+    this._bindEvents(element);
+    dropdownViews[this.id] = this;
+  }
+
+  $.extend(Dropdown, {
+    // Class methods
+    // -------------
+
+    createElement: function (option) {
+      var $parent = option.appendTo;
+      if (!($parent instanceof $)) { $parent = $($parent); }
+      var $el = $('<ul></ul>')
+        .addClass('dropdown-menu textcomplete-dropdown')
+        .attr('id', 'textcomplete-dropdown-' + option._oid)
+        .css({
+          display: 'none',
+          left: 0,
+          position: 'absolute',
+          zIndex: option.zIndex
+        })
+        .appendTo($parent);
+      return $el;
+    }
+  });
+
+  $.extend(Dropdown.prototype, {
+    // Public properties
+    // -----------------
+
+    $el:       null,  // jQuery object of ul.dropdown-menu element.
+    $inputEl:  null,  // jQuery object of target textarea.
+    completer: null,
+    footer:    null,
+    header:    null,
+    id:        null,
+    maxCount:  10,
+    placement: '',
+    shown:     false,
+    data:      [],     // Shown zipped data.
+    className: '',
+
+    // Public methods
+    // --------------
+
+    destroy: function () {
+      // Don't remove $el because it may be shared by several textcompletes.
+      this.deactivate();
+
+      this.$el.off('.' + this.id);
+      this.$inputEl.off('.' + this.id);
+      this.clear();
+      this.$el.remove();
+      this.$el = this.$inputEl = this.completer = null;
+      delete dropdownViews[this.id]
+    },
+
+    render: function (zippedData) {
+      var contentsHtml = this._buildContents(zippedData);
+      var unzippedData = $.map(this.data, function (d) { return d.value; });
+      if (this.data.length) {
+        var strategy = zippedData[0].strategy;
+        if (strategy.id) {
+          this.$el.attr('data-strategy', strategy.id);
+        } else {
+          this.$el.removeAttr('data-strategy');
+        }
+        this._renderHeader(unzippedData);
+        this._renderFooter(unzippedData);
+        if (contentsHtml) {
+          this._renderContents(contentsHtml);
+          this._fitToBottom();
+          this._fitToRight();
+          this._activateIndexedItem();
+        }
+        this._setScroll();
+      } else if (this.noResultsMessage) {
+        this._renderNoResultsMessage(unzippedData);
+      } else if (this.shown) {
+        this.deactivate();
+      }
+    },
+
+    setPosition: function (pos) {
+      // Make the dropdown fixed if the input is also fixed
+      // This can't be done during init, as textcomplete may be used on multiple elements on the same page
+      // Because the same dropdown is reused behind the scenes, we need to recheck every time the dropdown is showed
+      var position = 'absolute';
+      // Check if input or one of its parents has positioning we need to care about
+      this.$inputEl.add(this.$inputEl.parents()).each(function() {
+        if($(this).css('position') === 'absolute') // The element has absolute positioning, so it's all OK
+          return false;
+        if($(this).css('position') === 'fixed') {
+          pos.top -= $window.scrollTop();
+          pos.left -= $window.scrollLeft();					
+          position = 'fixed';
+          return false;
+        }
+      });
+      this.$el.css(this._applyPlacement(pos));
+      this.$el.css({ position: position }); // Update positioning
+
+      return this;
+    },
+
+    clear: function () {
+      this.$el.html('');
+      this.data = [];
+      this._index = 0;
+      this._$header = this._$footer = this._$noResultsMessage = null;
+    },
+
+    activate: function () {
+      if (!this.shown) {
+        this.clear();
+        this.$el.show();
+        if (this.className) { this.$el.addClass(this.className); }
+        this.completer.fire('textComplete:show');
+        this.shown = true;
+      }
+      return this;
+    },
+
+    deactivate: function () {
+      if (this.shown) {
+        this.$el.hide();
+        if (this.className) { this.$el.removeClass(this.className); }
+        this.completer.fire('textComplete:hide');
+        this.shown = false;
+      }
+      return this;
+    },
+
+    isUp: function (e) {
+      return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80);  // UP, Ctrl-P
+    },
+
+    isDown: function (e) {
+      return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78);  // DOWN, Ctrl-N
+    },
+
+    isEnter: function (e) {
+      var modifiers = e.ctrlKey || e.altKey || e.metaKey || e.shiftKey;
+      return !modifiers && (e.keyCode === 13 || e.keyCode === 9 || (this.option.completeOnSpace === true && e.keyCode === 32))  // ENTER, TAB
+    },
+
+    isPageup: function (e) {
+      return e.keyCode === 33;  // PAGEUP
+    },
+
+    isPagedown: function (e) {
+      return e.keyCode === 34;  // PAGEDOWN
+    },
+
+    isEscape: function (e) {
+      return e.keyCode === 27;  // ESCAPE
+    },
+
+    // Private properties
+    // ------------------
+
+    _data:    null,  // Currently shown zipped data.
+    _index:   null,
+    _$header: null,
+    _$noResultsMessage: null,
+    _$footer: null,
+
+    // Private methods
+    // ---------------
+
+    _bindEvents: function () {
+      this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this));
+      this.$el.on('touchstart.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this));
+      this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this));
+      this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this));
+    },
+
+    _onClick: function (e) {
+      var $el = $(e.target);
+      e.preventDefault();
+      e.originalEvent.keepTextCompleteDropdown = this.id;
+      if (!$el.hasClass('textcomplete-item')) {
+        $el = $el.closest('.textcomplete-item');
+      }
+      var datum = this.data[parseInt($el.data('index'), 10)];
+      this.completer.select(datum.value, datum.strategy, e);
+      var self = this;
+      // Deactive at next tick to allow other event handlers to know whether
+      // the dropdown has been shown or not.
+      setTimeout(function () {
+        self.deactivate();
+        if (e.type === 'touchstart') {
+          self.$inputEl.focus();
+        }
+      }, 0);
+    },
+
+    // Activate hovered item.
+    _onMouseover: function (e) {
+      var $el = $(e.target);
+      e.preventDefault();
+      if (!$el.hasClass('textcomplete-item')) {
+        $el = $el.closest('.textcomplete-item');
+      }
+      this._index = parseInt($el.data('index'), 10);
+      this._activateIndexedItem();
+    },
+
+    _onKeydown: function (e) {
+      if (!this.shown) { return; }
+
+      var command;
+
+      if ($.isFunction(this.option.onKeydown)) {
+        command = this.option.onKeydown(e, commands);
+      }
+
+      if (command == null) {
+        command = this._defaultKeydown(e);
+      }
+
+      switch (command) {
+        case commands.KEY_UP:
+          e.preventDefault();
+          this._up();
+          break;
+        case commands.KEY_DOWN:
+          e.preventDefault();
+          this._down();
+          break;
+        case commands.KEY_ENTER:
+          e.preventDefault();
+          this._enter(e);
+          break;
+        case commands.KEY_PAGEUP:
+          e.preventDefault();
+          this._pageup();
+          break;
+        case commands.KEY_PAGEDOWN:
+          e.preventDefault();
+          this._pagedown();
+          break;
+        case commands.KEY_ESCAPE:
+          e.preventDefault();
+          this.deactivate();
+          break;
+      }
+    },
+
+    _defaultKeydown: function (e) {
+      if (this.isUp(e)) {
+        return commands.KEY_UP;
+      } else if (this.isDown(e)) {
+        return commands.KEY_DOWN;
+      } else if (this.isEnter(e)) {
+        return commands.KEY_ENTER;
+      } else if (this.isPageup(e)) {
+        return commands.KEY_PAGEUP;
+      } else if (this.isPagedown(e)) {
+        return commands.KEY_PAGEDOWN;
+      } else if (this.isEscape(e)) {
+        return commands.KEY_ESCAPE;
+      }
+    },
+
+    _up: function () {
+      if (this._index === 0) {
+        this._index = this.data.length - 1;
+      } else {
+        this._index -= 1;
+      }
+      this._activateIndexedItem();
+      this._setScroll();
+    },
+
+    _down: function () {
+      if (this._index === this.data.length - 1) {
+        this._index = 0;
+      } else {
+        this._index += 1;
+      }
+      this._activateIndexedItem();
+      this._setScroll();
+    },
+
+    _enter: function (e) {
+      var datum = this.data[parseInt(this._getActiveElement().data('index'), 10)];
+      this.completer.select(datum.value, datum.strategy, e);
+      this.deactivate();
+    },
+
+    _pageup: function () {
+      var target = 0;
+      var threshold = this._getActiveElement().position().top - this.$el.innerHeight();
+      this.$el.children().each(function (i) {
+        if ($(this).position().top + $(this).outerHeight() > threshold) {
+          target = i;
+          return false;
+        }
+      });
+      this._index = target;
+      this._activateIndexedItem();
+      this._setScroll();
+    },
+
+    _pagedown: function () {
+      var target = this.data.length - 1;
+      var threshold = this._getActiveElement().position().top + this.$el.innerHeight();
+      this.$el.children().each(function (i) {
+        if ($(this).position().top > threshold) {
+          target = i;
+          return false
+        }
+      });
+      this._index = target;
+      this._activateIndexedItem();
+      this._setScroll();
+    },
+
+    _activateIndexedItem: function () {
+      this.$el.find('.textcomplete-item.active').removeClass('active');
+      this._getActiveElement().addClass('active');
+    },
+
+    _getActiveElement: function () {
+      return this.$el.children('.textcomplete-item:nth(' + this._index + ')');
+    },
+
+    _setScroll: function () {
+      var $activeEl = this._getActiveElement();
+      var itemTop = $activeEl.position().top;
+      var itemHeight = $activeEl.outerHeight();
+      var visibleHeight = this.$el.innerHeight();
+      var visibleTop = this.$el.scrollTop();
+      if (this._index === 0 || this._index == this.data.length - 1 || itemTop < 0) {
+        this.$el.scrollTop(itemTop + visibleTop);
+      } else if (itemTop + itemHeight > visibleHeight) {
+        this.$el.scrollTop(itemTop + itemHeight + visibleTop - visibleHeight);
+      }
+    },
+
+    _buildContents: function (zippedData) {
+      var datum, i, index;
+      var html = '';
+      for (i = 0; i < zippedData.length; i++) {
+        if (this.data.length === this.maxCount) break;
+        datum = zippedData[i];
+        if (include(this.data, datum)) { continue; }
+        index = this.data.length;
+        this.data.push(datum);
+        html += '<li class="textcomplete-item" data-index="' + index + '"><a>';
+        html +=   datum.strategy.template(datum.value, datum.term);
+        html += '</a></li>';
+      }
+      return html;
+    },
+
+    _renderHeader: function (unzippedData) {
+      if (this.header) {
+        if (!this._$header) {
+          this._$header = $('<li class="textcomplete-header"></li>').prependTo(this.$el);
+        }
+        var html = $.isFunction(this.header) ? this.header(unzippedData) : this.header;
+        this._$header.html(html);
+      }
+    },
+
+    _renderFooter: function (unzippedData) {
+      if (this.footer) {
+        if (!this._$footer) {
+          this._$footer = $('<li class="textcomplete-footer"></li>').appendTo(this.$el);
+        }
+        var html = $.isFunction(this.footer) ? this.footer(unzippedData) : this.footer;
+        this._$footer.html(html);
+      }
+    },
+
+    _renderNoResultsMessage: function (unzippedData) {
+      if (this.noResultsMessage) {
+        if (!this._$noResultsMessage) {
+          this._$noResultsMessage = $('<li class="textcomplete-no-results-message"></li>').appendTo(this.$el);
+        }
+        var html = $.isFunction(this.noResultsMessage) ? this.noResultsMessage(unzippedData) : this.noResultsMessage;
+        this._$noResultsMessage.html(html);
+      }
+    },
+
+    _renderContents: function (html) {
+      if (this._$footer) {
+        this._$footer.before(html);
+      } else {
+        this.$el.append(html);
+      }
+    },
+
+    _fitToBottom: function() {
+      var windowScrollBottom = $window.scrollTop() + $window.height();
+      var height = this.$el.height();
+      if ((this.$el.position().top + height) > windowScrollBottom) {
+        this.$el.offset({top: windowScrollBottom - height});
+      }
+    },
+
+    _fitToRight: function() {
+      // We don't know how wide our content is until the browser positions us, and at that point it clips us
+      // to the document width so we don't know if we would have overrun it. As a heuristic to avoid that clipping
+      // (which makes our elements wrap onto the next line and corrupt the next item), if we're close to the right
+      // edge, move left. We don't know how far to move left, so just keep nudging a bit.
+      var tolerance = 30; // pixels. Make wider than vertical scrollbar because we might not be able to use that space.
+      while (this.$el.offset().left + this.$el.width() > $window.width() - tolerance) {
+        this.$el.offset({left: this.$el.offset().left - tolerance});
+      }
+    },
+
+    _applyPlacement: function (position) {
+      // If the 'placement' option set to 'top', move the position above the element.
+      if (this.placement.indexOf('top') !== -1) {
+        // Overwrite the position object to set the 'bottom' property instead of the top.
+        position = {
+          top: 'auto',
+          bottom: this.$el.parent().height() - position.top + position.lineHeight,
+          left: position.left
+        };
+      } else {
+        position.bottom = 'auto';
+        delete position.lineHeight;
+      }
+      if (this.placement.indexOf('absleft') !== -1) {
+        position.left = 0;
+      } else if (this.placement.indexOf('absright') !== -1) {
+        position.right = 0;
+        position.left = 'auto';
+      }
+      return position;
+    }
+  });
+
+  $.fn.textcomplete.Dropdown = Dropdown;
+  $.extend($.fn.textcomplete, commands);
+}(jQuery);
+
++function ($) {
+  'use strict';
+
+  // Memoize a search function.
+  var memoize = function (func) {
+    var memo = {};
+    return function (term, callback) {
+      if (memo[term]) {
+        callback(memo[term]);
+      } else {
+        func.call(this, term, function (data) {
+          memo[term] = (memo[term] || []).concat(data);
+          callback.apply(null, arguments);
+        });
+      }
+    };
+  };
+
+  function Strategy(options) {
+    $.extend(this, options);
+    if (this.cache) { this.search = memoize(this.search); }
+  }
+
+  Strategy.parse = function (strategiesArray, params) {
+    return $.map(strategiesArray, function (strategy) {
+      var strategyObj = new Strategy(strategy);
+      strategyObj.el = params.el;
+      strategyObj.$el = params.$el;
+      return strategyObj;
+    });
+  };
+
+  $.extend(Strategy.prototype, {
+    // Public properties
+    // -----------------
+
+    // Required
+    match:      null,
+    replace:    null,
+    search:     null,
+
+    // Optional
+    id:         null,
+    cache:      false,
+    context:    function () { return true; },
+    index:      2,
+    template:   function (obj) { return obj; },
+    idProperty: null
+  });
+
+  $.fn.textcomplete.Strategy = Strategy;
+
+}(jQuery);
+
++function ($) {
+  'use strict';
+
+  var now = Date.now || function () { return new Date().getTime(); };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // `wait` msec.
+  //
+  // This utility function was originally implemented at Underscore.js.
+  var debounce = function (func, wait) {
+    var timeout, args, context, timestamp, result;
+    var later = function () {
+      var last = now() - timestamp;
+      if (last < wait) {
+        timeout = setTimeout(later, wait - last);
+      } else {
+        timeout = null;
+        result = func.apply(context, args);
+        context = args = null;
+      }
+    };
+
+    return function () {
+      context = this;
+      args = arguments;
+      timestamp = now();
+      if (!timeout) {
+        timeout = setTimeout(later, wait);
+      }
+      return result;
+    };
+  };
+
+  function Adapter () {}
+
+  $.extend(Adapter.prototype, {
+    // Public properties
+    // -----------------
+
+    id:        null, // Identity.
+    completer: null, // Completer object which creates it.
+    el:        null, // Textarea element.
+    $el:       null, // jQuery object of the textarea.
+    option:    null,
+
+    // Public methods
+    // --------------
+
+    initialize: function (element, completer, option) {
+      this.el        = element;
+      this.$el       = $(element);
+      this.id        = completer.id + this.constructor.name;
+      this.completer = completer;
+      this.option    = option;
+
+      if (this.option.debounce) {
+        this._onKeyup = debounce(this._onKeyup, this.option.debounce);
+      }
+
+      this._bindEvents();
+    },
+
+    destroy: function () {
+      this.$el.off('.' + this.id); // Remove all event handlers.
+      this.$el = this.el = this.completer = null;
+    },
+
+    // Update the element with the given value and strategy.
+    //
+    // value    - The selected object. It is one of the item of the array
+    //            which was callbacked from the search function.
+    // strategy - The Strategy associated with the selected value.
+    select: function (/* value, strategy */) {
+      throw new Error('Not implemented');
+    },
+
+    // Returns the caret's relative coordinates from body's left top corner.
+    getCaretPosition: function () {
+      var position = this._getCaretRelativePosition();
+      var offset = this.$el.offset();
+
+      // Calculate the left top corner of `this.option.appendTo` element.
+      var $parent = this.option.appendTo;
+      if ($parent) {
+         if (!($parent instanceof $)) { $parent = $($parent); }
+         var parentOffset = $parent.offsetParent().offset();
+         offset.top -= parentOffset.top;
+         offset.left -= parentOffset.left;
+      }
+
+      position.top += offset.top;
+      position.left += offset.left;
+      return position;
+    },
+
+    // Focus on the element.
+    focus: function () {
+      this.$el.focus();
+    },
+
+    // Private methods
+    // ---------------
+
+    _bindEvents: function () {
+      this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this));
+    },
+
+    _onKeyup: function (e) {
+      if (this._skipSearch(e)) { return; }
+      this.completer.trigger(this.getTextFromHeadToCaret(), true);
+    },
+
+    // Suppress searching if it returns true.
+    _skipSearch: function (clickEvent) {
+      switch (clickEvent.keyCode) {
+        case 9:  // TAB
+        case 13: // ENTER
+        case 40: // DOWN
+        case 38: // UP
+          return true;
+      }
+      if (clickEvent.ctrlKey) switch (clickEvent.keyCode) {
+        case 78: // Ctrl-N
+        case 80: // Ctrl-P
+          return true;
+      }
+    }
+  });
+
+  $.fn.textcomplete.Adapter = Adapter;
+}(jQuery);
+
++function ($) {
+  'use strict';
+
+  // Textarea adapter
+  // ================
+  //
+  // Managing a textarea. It doesn't know a Dropdown.
+  function Textarea(element, completer, option) {
+    this.initialize(element, completer, option);
+  }
+
+  $.extend(Textarea.prototype, $.fn.textcomplete.Adapter.prototype, {
+    // Public methods
+    // --------------
+
+    // Update the textarea with the given value and strategy.
+    select: function (value, strategy, e) {
+      var pre = this.getTextFromHeadToCaret();
+      var post = this.el.value.substring(this.el.selectionEnd);
+      var newSubstr = strategy.replace(value, e);
+      if (typeof newSubstr !== 'undefined') {
+        if ($.isArray(newSubstr)) {
+          post = newSubstr[1] + post;
+          newSubstr = newSubstr[0];
+        }
+        pre = pre.replace(strategy.match, newSubstr);
+        this.$el.val(pre + post);
+        this.el.selectionStart = this.el.selectionEnd = pre.length;
+      }
+    },
+
+    getTextFromHeadToCaret: function () {
+      return this.el.value.substring(0, this.el.selectionEnd);
+    },
+
+    // Private methods
+    // ---------------
+
+    _getCaretRelativePosition: function () {
+      var p = $.fn.textcomplete.getCaretCoordinates(this.el, this.el.selectionStart);
+      return {
+        top: p.top + parseInt(this.$el.css('line-height'), 10) - this.$el.scrollTop(),
+        left: p.left - this.$el.scrollLeft()
+      };
+    }
+  });
+
+  $.fn.textcomplete.Textarea = Textarea;
+}(jQuery);
+
++function ($) {
+  'use strict';
+
+  var sentinelChar = '吶';
+
+  function IETextarea(element, completer, option) {
+    this.initialize(element, completer, option);
+    $('<span>' + sentinelChar + '</span>').css({
+      position: 'absolute',
+      top: -9999,
+      left: -9999
+    }).insertBefore(element);
+  }
+
+  $.extend(IETextarea.prototype, $.fn.textcomplete.Textarea.prototype, {
+    // Public methods
+    // --------------
+
+    select: function (value, strategy, e) {
+      var pre = this.getTextFromHeadToCaret();
+      var post = this.el.value.substring(pre.length);
+      var newSubstr = strategy.replace(value, e);
+      if (typeof newSubstr !== 'undefined') {
+        if ($.isArray(newSubstr)) {
+          post = newSubstr[1] + post;
+          newSubstr = newSubstr[0];
+        }
+        pre = pre.replace(strategy.match, newSubstr);
+        this.$el.val(pre + post);
+        this.el.focus();
+        var range = this.el.createTextRange();
+        range.collapse(true);
+        range.moveEnd('character', pre.length);
+        range.moveStart('character', pre.length);
+        range.select();
+      }
+    },
+
+    getTextFromHeadToCaret: function () {
+      this.el.focus();
+      var range = document.selection.createRange();
+      range.moveStart('character', -this.el.value.length);
+      var arr = range.text.split(sentinelChar)
+      return arr.length === 1 ? arr[0] : arr[1];
+    }
+  });
+
+  $.fn.textcomplete.IETextarea = IETextarea;
+}(jQuery);
+
+// NOTE: TextComplete plugin has contenteditable support but it does not work
+//       fine especially on old IEs.
+//       Any pull requests are REALLY welcome.
+
++function ($) {
+  'use strict';
+
+  // ContentEditable adapter
+  // =======================
+  //
+  // Adapter for contenteditable elements.
+  function ContentEditable (element, completer, option) {
+    this.initialize(element, completer, option);
+  }
+
+  $.extend(ContentEditable.prototype, $.fn.textcomplete.Adapter.prototype, {
+    // Public methods
+    // --------------
+
+    // Update the content with the given value and strategy.
+    // When an dropdown item is selected, it is executed.
+    select: function (value, strategy, e) {
+      var pre = this.getTextFromHeadToCaret();
+      var sel = window.getSelection()
+      var range = sel.getRangeAt(0);
+      var selection = range.cloneRange();
+      selection.selectNodeContents(range.startContainer);
+      var content = selection.toString();
+      var post = content.substring(range.startOffset);
+      var newSubstr = strategy.replace(value, e);
+      if (typeof newSubstr !== 'undefined') {
+        if ($.isArray(newSubstr)) {
+          post = newSubstr[1] + post;
+          newSubstr = newSubstr[0];
+        }
+        pre = pre.replace(strategy.match, newSubstr);
+        range.selectNodeContents(range.startContainer);
+        range.deleteContents();
+        
+        // create temporary elements
+        var preWrapper = document.createElement("div");
+        preWrapper.innerHTML = pre;
+        var postWrapper = document.createElement("div");
+        postWrapper.innerHTML = post;
+        
+        // create the fragment thats inserted
+        var fragment = document.createDocumentFragment();
+        var childNode;
+        var lastOfPre;
+        while (childNode = preWrapper.firstChild) {
+        	lastOfPre = fragment.appendChild(childNode);
+        }
+        while (childNode = postWrapper.firstChild) {
+        	fragment.appendChild(childNode);
+        }
+        
+        // insert the fragment & jump behind the last node in "pre"
+        range.insertNode(fragment);
+        range.setStartAfter(lastOfPre);
+        
+        range.collapse(true);
+        sel.removeAllRanges();
+        sel.addRange(range);
+      }
+    },
+
+    // Private methods
+    // ---------------
+
+    // Returns the caret's relative position from the contenteditable's
+    // left top corner.
+    //
+    // Examples
+    //
+    //   this._getCaretRelativePosition()
+    //   //=> { top: 18, left: 200, lineHeight: 16 }
+    //
+    // Dropdown's position will be decided using the result.
+    _getCaretRelativePosition: function () {
+      var range = window.getSelection().getRangeAt(0).cloneRange();
+      var node = document.createElement('span');
+      range.insertNode(node);
+      range.selectNodeContents(node);
+      range.deleteContents();
+      var $node = $(node);
+      var position = $node.offset();
+      position.left -= this.$el.offset().left;
+      position.top += $node.height() - this.$el.offset().top;
+      position.lineHeight = $node.height();
+      $node.remove();
+      return position;
+    },
+
+    // Returns the string between the first character and the caret.
+    // Completer will be triggered with the result for start autocompleting.
+    //
+    // Example
+    //
+    //   // Suppose the html is '<b>hello</b> wor|ld' and | is the caret.
+    //   this.getTextFromHeadToCaret()
+    //   // => ' wor'  // not '<b>hello</b> wor'
+    getTextFromHeadToCaret: function () {
+      var range = window.getSelection().getRangeAt(0);
+      var selection = range.cloneRange();
+      selection.selectNodeContents(range.startContainer);
+      return selection.toString().substring(0, range.startOffset);
+    }
+  });
+
+  $.fn.textcomplete.ContentEditable = ContentEditable;
+}(jQuery);
+
+// The MIT License (MIT)
+// 
+// Copyright (c) 2015 Jonathan Ong me@jongleberry.com
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// https://github.com/component/textarea-caret-position
+
+(function () {
+
+// The properties that we copy into a mirrored div.
+// Note that some browsers, such as Firefox,
+// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
+// so we have to do every single property specifically.
+var properties = [
+  'direction',  // RTL support
+  'boxSizing',
+  'width',  // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
+  'height',
+  'overflowX',
+  'overflowY',  // copy the scrollbar for IE
+
+  'borderTopWidth',
+  'borderRightWidth',
+  'borderBottomWidth',
+  'borderLeftWidth',
+  'borderStyle',
+
+  'paddingTop',
+  'paddingRight',
+  'paddingBottom',
+  'paddingLeft',
+
+  // https://developer.mozilla.org/en-US/docs/Web/CSS/font
+  'fontStyle',
+  'fontVariant',
+  'fontWeight',
+  'fontStretch',
+  'fontSize',
+  'fontSizeAdjust',
+  'lineHeight',
+  'fontFamily',
+
+  'textAlign',
+  'textTransform',
+  'textIndent',
+  'textDecoration',  // might not make a difference, but better be safe
+
+  'letterSpacing',
+  'wordSpacing',
+
+  'tabSize',
+  'MozTabSize'
+
+];
+
+var isBrowser = (typeof window !== 'undefined');
+var isFirefox = (isBrowser && window.mozInnerScreenX != null);
+
+function getCaretCoordinates(element, position, options) {
+  if(!isBrowser) {
+    throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
+  }
+
+  var debug = options && options.debug || false;
+  if (debug) {
+    var el = document.querySelector('#input-textarea-caret-position-mirror-div');
+    if ( el ) { el.parentNode.removeChild(el); }
+  }
+
+  // mirrored div
+  var div = document.createElement('div');
+  div.id = 'input-textarea-caret-position-mirror-div';
+  document.body.appendChild(div);
+
+  var style = div.style;
+  var computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle;  // currentStyle for IE < 9
+
+  // default textarea styles
+  style.whiteSpace = 'pre-wrap';
+  if (element.nodeName !== 'INPUT')
+    style.wordWrap = 'break-word';  // only for textarea-s
+
+  // position off-screen
+  style.position = 'absolute';  // required to return coordinates properly
+  if (!debug)
+    style.visibility = 'hidden';  // not 'display: none' because we want rendering
+
+  // transfer the element's properties to the div
+  properties.forEach(function (prop) {
+    style[prop] = computed[prop];
+  });
+
+  if (isFirefox) {
+    // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
+    if (element.scrollHeight > parseInt(computed.height))
+      style.overflowY = 'scroll';
+  } else {
+    style.overflow = 'hidden';  // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
+  }
+
+  div.textContent = element.value.substring(0, position);
+  // the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
+  if (element.nodeName === 'INPUT')
+    div.textContent = div.textContent.replace(/\s/g, '\u00a0');
+
+  var span = document.createElement('span');
+  // Wrapping must be replicated *exactly*, including when a long word gets
+  // onto the next line, with whitespace at the end of the line before (#7).
+  // The  *only* reliable way to do that is to copy the *entire* rest of the
+  // textarea's content into the <span> created at the caret position.
+  // for inputs, just '.' would be enough, but why bother?
+  span.textContent = element.value.substring(position) || '.';  // || because a completely empty faux span doesn't render at all
+  div.appendChild(span);
+
+  var coordinates = {
+    top: span.offsetTop + parseInt(computed['borderTopWidth']),
+    left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
+  };
+
+  if (debug) {
+    span.style.backgroundColor = '#aaa';
+  } else {
+    document.body.removeChild(div);
+  }
+
+  return coordinates;
+}
+
+if (typeof module != 'undefined' && typeof module.exports != 'undefined') {
+  module.exports = getCaretCoordinates;
+} else if(isBrowser){
+  window.$.fn.textcomplete.getCaretCoordinates = getCaretCoordinates;
+}
+
+}());
+
+return jQuery;
+}));
\ No newline at end of file
diff --git a/public/vendor/jquery/textcomplete/jquery.textcomplete.min.js b/public/vendor/jquery/textcomplete/jquery.textcomplete.min.js
deleted file mode 100644
index 5ac596b9a4..0000000000
--- a/public/vendor/jquery/textcomplete/jquery.textcomplete.min.js
+++ /dev/null
@@ -1 +0,0 @@
-/*! jquery-textcomplete - v0.7.3 - 2015-08-31 */!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"==typeof module&&module.exports){var b=require("jquery");module.exports=a(b)}else a(jQuery)}(function(a){if("undefined"==typeof a)throw new Error("jQuery.textcomplete requires jQuery");return+function(a){"use strict";var b=function(a){console.warn&&console.warn(a)},c=1;a.fn.textcomplete=function(d,e){var f=Array.prototype.slice.call(arguments);return this.each(function(){var g=this,h=a(this),i=h.data("textComplete");if(i||(e||(e={}),e._oid=c++,i=new a.fn.textcomplete.Completer(this,e),h.data("textComplete",i)),"string"==typeof d){if(!i)return;f.shift(),i[d].apply(i,f),"destroy"===d&&h.removeData("textComplete")}else a.each(d,function(c){a.each(["header","footer","placement","maxCount"],function(a){c[a]&&(i.option[a]=c[a],b(a+"as a strategy param is deprecated. Use option."),delete c[a])})}),i.register(a.fn.textcomplete.Strategy.parse(d,{el:g,$el:h}))})}}(a),+function(a){"use strict";function b(c,d){if(this.$el=a(c),this.id="textcomplete"+f++,this.strategies=[],this.views=[],this.option=a.extend({},b._getDefaults(),d),!this.$el.is("input[type=text]")&&!this.$el.is("textarea")&&!c.isContentEditable&&"true"!=c.contentEditable)throw new Error("textcomplete must be called on a Textarea or a ContentEditable.");if(c===document.activeElement)this.initialize();else{var e=this;this.$el.one("focus."+this.id,function(){e.initialize()})}}var c=function(a){var b,c;return function(){var d=Array.prototype.slice.call(arguments);if(b)return c=d,void 0;b=!0;var e=this;d.unshift(function f(){if(c){var d=c;c=void 0,d.unshift(f),a.apply(e,d)}else b=!1}),a.apply(this,d)}},d=function(a){return"[object String]"===Object.prototype.toString.call(a)},e=function(a){return"[object Function]"===Object.prototype.toString.call(a)},f=0;b._getDefaults=function(){return b.DEFAULTS||(b.DEFAULTS={appendTo:a("body"),zIndex:"100"}),b.DEFAULTS},a.extend(b.prototype,{id:null,option:null,strategies:null,adapter:null,dropdown:null,$el:null,initialize:function(){var b=this.$el.get(0);this.dropdown=new a.fn.textcomplete.Dropdown(b,this,this.option);var c,d;this.option.adapter?c=this.option.adapter:(d=this.$el.is("textarea")||this.$el.is("input[type=text]")?"number"==typeof b.selectionEnd?"Textarea":"IETextarea":"ContentEditable",c=a.fn.textcomplete[d]),this.adapter=new c(b,this,this.option)},destroy:function(){this.$el.off("."+this.id),this.adapter&&this.adapter.destroy(),this.dropdown&&this.dropdown.destroy(),this.$el=this.adapter=this.dropdown=null},trigger:function(a,b){this.dropdown||this.initialize(),null!=a||(a=this.adapter.getTextFromHeadToCaret());var c=this._extractSearchQuery(a);if(c.length){var d=c[1];if(b&&this._term===d)return;this._term=d,this._search.apply(this,c)}else this._term=null,this.dropdown.deactivate()},fire:function(a){var b=Array.prototype.slice.call(arguments,1);return this.$el.trigger(a,b),this},register:function(a){Array.prototype.push.apply(this.strategies,a)},select:function(a,b,c){this._term=null,this.adapter.select(a,b,c),this.fire("change").fire("textComplete:select",a,b),this.adapter.focus()},_clearAtNext:!0,_term:null,_extractSearchQuery:function(a){for(var b=0;b<this.strategies.length;b++){var c=this.strategies[b],f=c.context(a);if(f||""===f){var g=e(c.match)?c.match(a):c.match;d(f)&&(a=f);var h=a.match(g);if(h)return[c,h[c.index],h]}}return[]},_search:c(function(a,b,c,d){var e=this;b.search(c,function(d,f){e.dropdown.shown||e.dropdown.activate(),e._clearAtNext&&(e.dropdown.clear(),e._clearAtNext=!1),e.dropdown.setPosition(e.adapter.getCaretPosition()),e.dropdown.render(e._zip(d,b,c)),f||(a(),e._clearAtNext=!0)},d)}),_zip:function(b,c,d){return a.map(b,function(a){return{value:a,strategy:c,term:d}})}}),a.fn.textcomplete.Completer=b}(a),+function(a){"use strict";function b(c,d,f){this.$el=b.createElement(f),this.completer=d,this.id=d.id+"dropdown",this._data=[],this.$inputEl=a(c),this.option=f,f.listPosition&&(this.setPosition=f.listPosition),f.height&&this.$el.height(f.height);var g=this;a.each(["maxCount","placement","footer","header","noResultsMessage","className"],function(a,b){null!=f[b]&&(g[b]=f[b])}),this._bindEvents(c),e[this.id]=this}var c=a(window),d=function(a,b){var c,d,e=b.strategy.idProperty;for(c=0;c<a.length;c++)if(d=a[c],d.strategy===b.strategy)if(e){if(d.value[e]===b.value[e])return!0}else if(d.value===b.value)return!0;return!1},e={};a(document).on("click",function(b){var c=b.originalEvent&&b.originalEvent.keepTextCompleteDropdown;a.each(e,function(a,b){a!==c&&b.deactivate()})});var f={SKIP_DEFAULT:0,KEY_UP:1,KEY_DOWN:2,KEY_ENTER:3,KEY_PAGEUP:4,KEY_PAGEDOWN:5,KEY_ESCAPE:6};a.extend(b,{createElement:function(b){var c=b.appendTo;c instanceof a||(c=a(c));var d=a("<ul></ul>").addClass("dropdown-menu textcomplete-dropdown").attr("id","textcomplete-dropdown-"+b._oid).css({display:"none",left:0,position:"absolute",zIndex:b.zIndex}).appendTo(c);return d}}),a.extend(b.prototype,{$el:null,$inputEl:null,completer:null,footer:null,header:null,id:null,maxCount:10,placement:"",shown:!1,data:[],className:"",destroy:function(){this.deactivate(),this.$el.off("."+this.id),this.$inputEl.off("."+this.id),this.clear(),this.$el=this.$inputEl=this.completer=null,delete e[this.id]},render:function(b){var c=this._buildContents(b),d=a.map(this.data,function(a){return a.value});this.data.length?(this._renderHeader(d),this._renderFooter(d),c&&(this._renderContents(c),this._fitToBottom(),this._activateIndexedItem()),this._setScroll()):this.noResultsMessage?this._renderNoResultsMessage(d):this.shown&&this.deactivate()},setPosition:function(b){this.$el.css(this._applyPlacement(b));var c="absolute";return this.$inputEl.add(this.$inputEl.parents()).each(function(){return"absolute"===a(this).css("position")?!1:"fixed"===a(this).css("position")?(c="fixed",!1):void 0}),this.$el.css({position:c}),this},clear:function(){this.$el.html(""),this.data=[],this._index=0,this._$header=this._$footer=this._$noResultsMessage=null},activate:function(){return this.shown||(this.clear(),this.$el.show(),this.className&&this.$el.addClass(this.className),this.completer.fire("textComplete:show"),this.shown=!0),this},deactivate:function(){return this.shown&&(this.$el.hide(),this.className&&this.$el.removeClass(this.className),this.completer.fire("textComplete:hide"),this.shown=!1),this},isUp:function(a){return 38===a.keyCode||a.ctrlKey&&80===a.keyCode},isDown:function(a){return 40===a.keyCode||a.ctrlKey&&78===a.keyCode},isEnter:function(a){var b=a.ctrlKey||a.altKey||a.metaKey||a.shiftKey;return!b&&(13===a.keyCode||9===a.keyCode||this.option.completeOnSpace===!0&&32===a.keyCode)},isPageup:function(a){return 33===a.keyCode},isPagedown:function(a){return 34===a.keyCode},isEscape:function(a){return 27===a.keyCode},_data:null,_index:null,_$header:null,_$noResultsMessage:null,_$footer:null,_bindEvents:function(){this.$el.on("mousedown."+this.id,".textcomplete-item",a.proxy(this._onClick,this)),this.$el.on("touchstart."+this.id,".textcomplete-item",a.proxy(this._onClick,this)),this.$el.on("mouseover."+this.id,".textcomplete-item",a.proxy(this._onMouseover,this)),this.$inputEl.on("keydown."+this.id,a.proxy(this._onKeydown,this))},_onClick:function(b){var c=a(b.target);b.preventDefault(),b.originalEvent.keepTextCompleteDropdown=this.id,c.hasClass("textcomplete-item")||(c=c.closest(".textcomplete-item"));var d=this.data[parseInt(c.data("index"),10)];this.completer.select(d.value,d.strategy,b);var e=this;setTimeout(function(){e.deactivate(),"touchstart"===b.type&&e.$inputEl.focus()},0)},_onMouseover:function(b){var c=a(b.target);b.preventDefault(),c.hasClass("textcomplete-item")||(c=c.closest(".textcomplete-item")),this._index=parseInt(c.data("index"),10),this._activateIndexedItem()},_onKeydown:function(b){if(this.shown){var c;switch(a.isFunction(this.option.onKeydown)&&(c=this.option.onKeydown(b,f)),null==c&&(c=this._defaultKeydown(b)),c){case f.KEY_UP:b.preventDefault(),this._up();break;case f.KEY_DOWN:b.preventDefault(),this._down();break;case f.KEY_ENTER:b.preventDefault(),this._enter(b);break;case f.KEY_PAGEUP:b.preventDefault(),this._pageup();break;case f.KEY_PAGEDOWN:b.preventDefault(),this._pagedown();break;case f.KEY_ESCAPE:b.preventDefault(),this.deactivate()}}},_defaultKeydown:function(a){return this.isUp(a)?f.KEY_UP:this.isDown(a)?f.KEY_DOWN:this.isEnter(a)?f.KEY_ENTER:this.isPageup(a)?f.KEY_PAGEUP:this.isPagedown(a)?f.KEY_PAGEDOWN:this.isEscape(a)?f.KEY_ESCAPE:void 0},_up:function(){0===this._index?this._index=this.data.length-1:this._index-=1,this._activateIndexedItem(),this._setScroll()},_down:function(){this._index===this.data.length-1?this._index=0:this._index+=1,this._activateIndexedItem(),this._setScroll()},_enter:function(a){var b=this.data[parseInt(this._getActiveElement().data("index"),10)];this.completer.select(b.value,b.strategy,a),this.deactivate()},_pageup:function(){var b=0,c=this._getActiveElement().position().top-this.$el.innerHeight();this.$el.children().each(function(d){return a(this).position().top+a(this).outerHeight()>c?(b=d,!1):void 0}),this._index=b,this._activateIndexedItem(),this._setScroll()},_pagedown:function(){var b=this.data.length-1,c=this._getActiveElement().position().top+this.$el.innerHeight();this.$el.children().each(function(d){return a(this).position().top>c?(b=d,!1):void 0}),this._index=b,this._activateIndexedItem(),this._setScroll()},_activateIndexedItem:function(){this.$el.find(".textcomplete-item.active").removeClass("active"),this._getActiveElement().addClass("active")},_getActiveElement:function(){return this.$el.children(".textcomplete-item:nth("+this._index+")")},_setScroll:function(){var a=this._getActiveElement(),b=a.position().top,c=a.outerHeight(),d=this.$el.innerHeight(),e=this.$el.scrollTop();0===this._index||this._index==this.data.length-1||0>b?this.$el.scrollTop(b+e):b+c>d&&this.$el.scrollTop(b+c+e-d)},_buildContents:function(a){var b,c,e,f="";for(c=0;c<a.length&&this.data.length!==this.maxCount;c++)b=a[c],d(this.data,b)||(e=this.data.length,this.data.push(b),f+='<li class="textcomplete-item" data-index="'+e+'"><a>',f+=b.strategy.template(b.value,b.term),f+="</a></li>");return f},_renderHeader:function(b){if(this.header){this._$header||(this._$header=a('<li class="textcomplete-header"></li>').prependTo(this.$el));var c=a.isFunction(this.header)?this.header(b):this.header;this._$header.html(c)}},_renderFooter:function(b){if(this.footer){this._$footer||(this._$footer=a('<li class="textcomplete-footer"></li>').appendTo(this.$el));var c=a.isFunction(this.footer)?this.footer(b):this.footer;this._$footer.html(c)}},_renderNoResultsMessage:function(b){if(this.noResultsMessage){this._$noResultsMessage||(this._$noResultsMessage=a('<li class="textcomplete-no-results-message"></li>').appendTo(this.$el));var c=a.isFunction(this.noResultsMessage)?this.noResultsMessage(b):this.noResultsMessage;this._$noResultsMessage.html(c)}},_renderContents:function(a){this._$footer?this._$footer.before(a):this.$el.append(a)},_fitToBottom:function(){var a=c.scrollTop()+c.height(),b=this.$el.height();this.$el.position().top+b>a&&this.$el.offset({top:a-b})},_applyPlacement:function(a){return-1!==this.placement.indexOf("top")?a={top:"auto",bottom:this.$el.parent().height()-a.top+a.lineHeight,left:a.left}:(a.bottom="auto",delete a.lineHeight),-1!==this.placement.indexOf("absleft")?a.left=0:-1!==this.placement.indexOf("absright")&&(a.right=0,a.left="auto"),a}}),a.fn.textcomplete.Dropdown=b,a.extend(a.fn.textcomplete,f)}(a),+function(a){"use strict";function b(b){a.extend(this,b),this.cache&&(this.search=c(this.search))}var c=function(a){var b={};return function(c,d){b[c]?d(b[c]):a.call(this,c,function(a){b[c]=(b[c]||[]).concat(a),d.apply(null,arguments)})}};b.parse=function(c,d){return a.map(c,function(a){var c=new b(a);return c.el=d.el,c.$el=d.$el,c})},a.extend(b.prototype,{match:null,replace:null,search:null,cache:!1,context:function(){return!0},index:2,template:function(a){return a},idProperty:null}),a.fn.textcomplete.Strategy=b}(a),+function(a){"use strict";function b(){}var c=Date.now||function(){return(new Date).getTime()},d=function(a,b){var d,e,f,g,h,i=function(){var j=c()-g;b>j?d=setTimeout(i,b-j):(d=null,h=a.apply(f,e),f=e=null)};return function(){return f=this,e=arguments,g=c(),d||(d=setTimeout(i,b)),h}};a.extend(b.prototype,{id:null,completer:null,el:null,$el:null,option:null,initialize:function(b,c,e){this.el=b,this.$el=a(b),this.id=c.id+this.constructor.name,this.completer=c,this.option=e,this.option.debounce&&(this._onKeyup=d(this._onKeyup,this.option.debounce)),this._bindEvents()},destroy:function(){this.$el.off("."+this.id),this.$el=this.el=this.completer=null},select:function(){throw new Error("Not implemented")},getCaretPosition:function(){var a=this._getCaretRelativePosition(),b=this.$el.offset();return a.top+=b.top,a.left+=b.left,a},focus:function(){this.$el.focus()},_bindEvents:function(){this.$el.on("keyup."+this.id,a.proxy(this._onKeyup,this))},_onKeyup:function(a){this._skipSearch(a)||this.completer.trigger(this.getTextFromHeadToCaret(),!0)},_skipSearch:function(a){switch(a.keyCode){case 13:case 40:case 38:return!0}if(a.ctrlKey)switch(a.keyCode){case 78:case 80:return!0}}}),a.fn.textcomplete.Adapter=b}(a),+function(a){"use strict";function b(a,b,c){this.initialize(a,b,c)}b.DIV_PROPERTIES={left:-9999,position:"absolute",top:0,whiteSpace:"pre-wrap"},b.COPY_PROPERTIES=["border-width","font-family","font-size","font-style","font-variant","font-weight","height","letter-spacing","word-spacing","line-height","text-decoration","text-align","width","padding-top","padding-right","padding-bottom","padding-left","margin-top","margin-right","margin-bottom","margin-left","border-style","box-sizing","tab-size"],a.extend(b.prototype,a.fn.textcomplete.Adapter.prototype,{select:function(b,c,d){var e=this.getTextFromHeadToCaret(),f=this.el.value.substring(this.el.selectionEnd),g=c.replace(b,d);"undefined"!=typeof g&&(a.isArray(g)&&(f=g[1]+f,g=g[0]),e=e.replace(c.match,g),this.$el.val(e+f),this.el.selectionStart=this.el.selectionEnd=e.length)},_getCaretRelativePosition:function(){var b=a("<div></div>").css(this._copyCss()).text(this.getTextFromHeadToCaret()),c=a("<span></span>").text(".").appendTo(b);this.$el.before(b);var d=c.position();return d.top+=c.height()-this.$el.scrollTop(),d.lineHeight=c.height(),b.remove(),d},_copyCss:function(){return a.extend({overflow:this.el.scrollHeight>this.el.offsetHeight?"scroll":"auto"},b.DIV_PROPERTIES,this._getStyles())},_getStyles:function(a){var c=a("<div></div>").css(["color"]).color;return"undefined"!=typeof c?function(){return this.$el.css(b.COPY_PROPERTIES)}:function(){var c=this.$el,d={};return a.each(b.COPY_PROPERTIES,function(a,b){d[b]=c.css(b)}),d}}(a),getTextFromHeadToCaret:function(){return this.el.value.substring(0,this.el.selectionEnd)}}),a.fn.textcomplete.Textarea=b}(a),+function(a){"use strict";function b(b,d,e){this.initialize(b,d,e),a("<span>"+c+"</span>").css({position:"absolute",top:-9999,left:-9999}).insertBefore(b)}var c="?";a.extend(b.prototype,a.fn.textcomplete.Textarea.prototype,{select:function(b,c,d){var e=this.getTextFromHeadToCaret(),f=this.el.value.substring(e.length),g=c.replace(b,d);if("undefined"!=typeof g){a.isArray(g)&&(f=g[1]+f,g=g[0]),e=e.replace(c.match,g),this.$el.val(e+f),this.el.focus();var h=this.el.createTextRange();h.collapse(!0),h.moveEnd("character",e.length),h.moveStart("character",e.length),h.select()}},getTextFromHeadToCaret:function(){this.el.focus();var a=document.selection.createRange();a.moveStart("character",-this.el.value.length);var b=a.text.split(c);return 1===b.length?b[0]:b[1]}}),a.fn.textcomplete.IETextarea=b}(a),+function(a){"use strict";function b(a,b,c){this.initialize(a,b,c)}a.extend(b.prototype,a.fn.textcomplete.Adapter.prototype,{select:function(b,c,d){var e=this.getTextFromHeadToCaret(),f=window.getSelection(),g=f.getRangeAt(0),h=g.cloneRange();h.selectNodeContents(g.startContainer);var i=h.toString(),j=i.substring(g.startOffset),k=c.replace(b,d);if("undefined"!=typeof k){a.isArray(k)&&(j=k[1]+j,k=k[0]),e=e.replace(c.match,k),g.selectNodeContents(g.startContainer),g.deleteContents();var l=document.createTextNode(e+j);g.insertNode(l),g.setStart(l,e.length),g.collapse(!0),f.removeAllRanges(),f.addRange(g)}},_getCaretRelativePosition:function(){var b=window.getSelection().getRangeAt(0).cloneRange(),c=document.createElement("span");b.insertNode(c),b.selectNodeContents(c),b.deleteContents();var d=a(c),e=d.offset();return e.left-=this.$el.offset().left,e.top+=d.height()-this.$el.offset().top,e.lineHeight=d.height(),d.remove(),e},getTextFromHeadToCaret:function(){var a=window.getSelection().getRangeAt(0),b=a.cloneRange();return b.selectNodeContents(a.startContainer),b.toString().substring(0,a.startOffset)}}),a.fn.textcomplete.ContentEditable=b}(a),a});
\ No newline at end of file
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.de-short.js b/public/vendor/jquery/timeago/locales/jquery.timeago.de-short.js
new file mode 100644
index 0000000000..10f158de08
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.de-short.js
@@ -0,0 +1,20 @@
+// German shortened
+jQuery.timeago.settings.strings = {
+  prefixAgo: null,
+  prefixFromNow: null,
+  suffixAgo: "",
+  suffixFromNow: "",
+  seconds: "sec",
+  minute: "1min",
+  minutes: "%dmin",
+  hour: "1h",
+  hours: "%dh",
+  day: "1d",
+  days: "%dd",
+  month: "1Mon",
+  months: "%dMon",
+  year: "1Jhr",
+  years: "%dJhr",
+  wordSeparator: " ",
+  numbers: []
+};
diff --git a/src/analytics.js b/src/analytics.js
index c907d784a8..c1ede42eba 100644
--- a/src/analytics.js
+++ b/src/analytics.js
@@ -2,6 +2,7 @@
 
 var cronJob = require('cron').CronJob;
 var async = require('async');
+var winston = require('winston');
 
 var db = require('./database');
 
@@ -84,8 +85,10 @@ var db = require('./database');
 
 		if (Object.keys(counters).length > 0) {
 			for(var key in counters) {
-				dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:' + key, counters[key], today.getTime()));
-				delete counters[key];
+				if (counters.hasOwnProperty(key)) {					
+					dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:' + key, counters[key], today.getTime()));
+					delete counters[key];
+				}
 			}
 		}
 
diff --git a/src/batch.js b/src/batch.js
index 70ccd8df01..1a425e1a21 100644
--- a/src/batch.js
+++ b/src/batch.js
@@ -23,6 +23,11 @@ var async = require('async'),
 			return callback(new Error('[[error:process-not-a-function]]'));
 		}
 
+		// use the fast path if possible
+		if (db.processSortedSet && typeof options.doneIf !== 'function' && !utils.isNumber(options.alwaysStartAt)) {
+			return db.processSortedSet(setKey, process, options.batch || DEFAULT_BATCH_SIZE, callback);
+		}
+
 		// custom done condition
 		options.doneIf = typeof options.doneIf === 'function' ? options.doneIf : function(){};
 
@@ -58,4 +63,4 @@ var async = require('async'),
 		);
 	};
 
-}(exports));
\ No newline at end of file
+}(exports));
diff --git a/src/categories/create.js b/src/categories/create.js
index fbd448407d..7f1f3955f7 100644
--- a/src/categories/create.js
+++ b/src/categories/create.js
@@ -1,10 +1,12 @@
 'use strict';
 
-var async = require('async'),
-	db = require('../database'),
-	privileges = require('../privileges'),
-	plugins = require('../plugins'),
-	utils = require('../../public/src/utils');
+var async = require('async');
+
+var db = require('../database');
+var privileges = require('../privileges');
+var groups = require('../groups');
+var plugins = require('../plugins');
+var utils = require('../../public/src/utils');
 
 module.exports = function(Categories) {
 
@@ -17,15 +19,16 @@ module.exports = function(Categories) {
 				db.incrObjectField('global', 'nextCid', next);
 			},
 			function(cid, next) {
-				var slug = cid + '/' + utils.slugify(data.name),
-					order = data.order || cid,	// If no order provided, place it at the end
-					colours = Categories.assignColours();
+				data.name = data.name || 'Category ' + cid;
+				var slug = cid + '/' + utils.slugify(data.name);
+				var order = data.order || cid;	// If no order provided, place it at the end
+				var colours = Categories.assignColours();
 
 				category = {
 					cid: cid,
 					name: data.name,
-					description: ( data.description ? data.description : '' ),
-					icon: ( data.icon ? data.icon : '' ),
+					description: data.description ? data.description : '',
+					icon: data.icon ? data.icon : '',
 					bgColor: data.bgColor || colours[0],
 					color: data.color || colours[1],
 					slug: slug,
@@ -49,6 +52,7 @@ module.exports = function(Categories) {
 
 				async.series([
 					async.apply(db.setObject, 'category:' + category.cid, category),
+					async.apply(Categories.parseDescription, category.cid, category.description),
 					async.apply(db.sortedSetAdd, 'categories:cid', category.order, category.cid),
 					async.apply(db.sortedSetAdd, 'cid:' + parentCid + ':children', category.order, category.cid),
 					async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'administrators'),
@@ -57,6 +61,12 @@ module.exports = function(Categories) {
 				], next);
 			},
 			function(results, next) {
+				if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) {
+					return Categories.copySettingsFrom(data.cloneFromCid, category.cid, next);
+				}
+				next(null, category);
+			},
+			function(category, next) {
 				plugins.fireHook('action:category.create', category);
 				next(null, category);
 			}
@@ -64,10 +74,94 @@ module.exports = function(Categories) {
 	};
 
 	Categories.assignColours = function() {
-		var backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'],
-			text = ['#fff', '#fff', '#333', '#fff', '#333', '#fff', '#fff', '#fff'],
-			index = Math.floor(Math.random() * backgrounds.length);
+		var backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'];
+		var text = ['#fff', '#fff', '#333', '#fff', '#333', '#fff', '#fff', '#fff'];
+		var index = Math.floor(Math.random() * backgrounds.length);
 
 		return [backgrounds[index], text[index]];
 	};
+
+	Categories.copySettingsFrom = function(fromCid, toCid, callback) {
+		var destination;
+		async.waterfall([
+			function (next) {
+				async.parallel({
+					source: async.apply(db.getObject, 'category:' + fromCid),
+					destination: async.apply(db.getObject, 'category:' + toCid)
+				}, next);
+			},
+			function (results, next) {
+				if (!results.source) {
+					return next(new Error('[[error:invalid-cid]]'));
+				}
+				destination = results.destination;
+
+				var tasks = [];
+				if (parseInt(results.source.parentCid, 10)) {
+					tasks.push(async.apply(db.sortedSetAdd, 'cid:' + results.source.parentCid + ':children', results.source.order, toCid));
+				}
+
+				if (destination && parseInt(destination.parentCid, 10)) {
+					tasks.push(async.apply(db.sortedSetRemove, 'cid:' + destination.parentCid + ':children', toCid));
+				}
+
+				destination.description = results.source.description;
+				destination.descriptionParsed = results.source.descriptionParsed;
+				destination.icon = results.source.icon;
+				destination.bgColor = results.source.bgColor;
+				destination.color = results.source.color;
+				destination.link = results.source.link;
+				destination.numRecentReplies = results.source.numRecentReplies;
+				destination.class = results.source.class;
+				destination.imageClass = results.source.imageClass;
+				destination.parentCid = results.source.parentCid || 0;
+
+				tasks.push(async.apply(db.setObject, 'category:' + toCid, destination));
+
+				async.series(tasks, next);
+			},
+			function (results, next) {
+				Categories.copyPrivilegesFrom(fromCid, toCid, next);
+			}
+		], function(err) {
+			callback(err, destination);
+		});
+	};
+
+	Categories.copyPrivilegesFrom = function(fromCid, toCid, callback) {
+		var privilegeList = [
+			'find', 'read', 'topics:create', 'topics:reply', 'purge', 'mods',
+			'groups:find', 'groups:read', 'groups:topics:create', 'groups:topics:reply', 'groups:purge', 'groups:moderate'
+		];
+
+		async.each(privilegeList, function(privilege, next) {
+			copyPrivilege(privilege, fromCid, toCid, next);
+		}, callback);
+	};
+
+	function copyPrivilege(privilege, fromCid, toCid, callback) {
+		async.waterfall([
+			function (next) {
+				db.getSortedSetRange('group:cid:' + toCid + ':privileges:' + privilege + ':members', 0, -1, next);
+			},
+			function (currentMembers, next) {
+				async.eachSeries(currentMembers, function(member, next) {
+					groups.leave('cid:' + toCid + ':privileges:' + privilege, member, next);
+				}, next);
+			},
+			function (next) {
+				db.getSortedSetRange('group:cid:' + fromCid + ':privileges:' + privilege + ':members', 0, -1, next);
+			},
+			function (members, next) {
+				if (!members || !members.length) {
+					return callback();
+				}
+
+				async.eachSeries(members, function(member, next) {
+					groups.join('cid:' + toCid + ':privileges:' + privilege, member, next);
+				}, next);
+			}
+		], callback);
+	}
+
 };
diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js
index c85bd5a69a..7d42f053ff 100644
--- a/src/categories/recentreplies.js
+++ b/src/categories/recentreplies.js
@@ -86,7 +86,7 @@ module.exports = function(Categories) {
 				db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, -1, '+inf', Date.now(), next);
 			},
 			tids: function(next) {
-				db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, Math.max(1, count), Date.now(), 0, next);
+				db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, Math.max(1, count), Date.now(), '-inf', next);
 			}
 		}, function(err, results) {
 			if (err) {
diff --git a/src/categories/topics.js b/src/categories/topics.js
index b38a861f91..8ee7105256 100644
--- a/src/categories/topics.js
+++ b/src/categories/topics.js
@@ -53,18 +53,10 @@ module.exports = function(Categories) {
 	};
 
 	Categories.getTopicIds = function(set, reverse, start, stop, callback) {
-		if (Array.isArray(set)) {
-			if (reverse) {
-				db.getSortedSetRevUnion(set, start, stop, callback);
-			} else {
-				db.getSortedSetUnion(set, start, stop, callback);
-			}
+		if (reverse) {
+			db.getSortedSetRevRange(set, start, stop, callback);
 		} else {
-			if (reverse) {
-				db.getSortedSetRevRange(set, start, stop, callback);
-			} else {
-				db.getSortedSetRange(set, start, stop, callback);
-			}
+			db.getSortedSetRange(set, start, stop, callback);
 		}
 	};
 
diff --git a/src/categories/update.js b/src/categories/update.js
index ffd24e7d98..78e97e4076 100644
--- a/src/categories/update.js
+++ b/src/categories/update.js
@@ -4,6 +4,7 @@
 var async = require('async'),
 	db = require('../database'),
 	utils = require('../../public/src/utils'),
+	translator = require('../../public/src/modules/translator'),
 	plugins = require('../plugins');
 
 module.exports = function(Categories) {
@@ -27,7 +28,9 @@ module.exports = function(Categories) {
 
 
 			if (modifiedFields.hasOwnProperty('name')) {
-				modifiedFields.slug = cid + '/' + utils.slugify(modifiedFields.name);
+				translator.translate(modifiedFields.name, function(translated) {
+					modifiedFields.slug = cid + '/' + utils.slugify(translated);
+				});
 			}
 
 			plugins.fireHook('filter:category.update', {category: modifiedFields}, function(err, categoryData) {
@@ -69,7 +72,7 @@ module.exports = function(Categories) {
 			if (key === 'order') {
 				updateOrder(cid, value, callback);
 			} else if (key === 'description') {
-				parseDescription(cid, value, callback);
+				Categories.parseDescription(cid, value, callback);
 			} else {
 				callback();
 			}
@@ -97,7 +100,7 @@ module.exports = function(Categories) {
 				function (next) {
 					db.setObjectField('category:' + cid, 'parentCid', newParent, next);
 				}
-			], function(err, results) {
+			], function(err) {
 				callback(err);
 			});
 		});
@@ -121,13 +124,13 @@ module.exports = function(Categories) {
 		});
 	}
 
-	function parseDescription(cid, description, callback) {
+	Categories.parseDescription = function(cid, description, callback) {
 		plugins.fireHook('filter:parse.raw', description, function(err, parsedDescription) {
 			if (err) {
 				return callback(err);
 			}
 			Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription, callback);
 		});
-	}
+	};
 
 };
diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js
index f2c579a05e..8acbdc08c8 100644
--- a/src/controllers/accounts/helpers.js
+++ b/src/controllers/accounts/helpers.js
@@ -58,23 +58,25 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
 			var userSettings = results.userSettings;
 			var isAdmin = results.isAdmin;
 			var isGlobalModerator = results.isGlobalModerator;
-			var self = parseInt(callerUID, 10) === parseInt(userData.uid, 10);
+			var isSelf = parseInt(callerUID, 10) === parseInt(userData.uid, 10);
 
 			userData.joindateISO = utils.toISOString(userData.joindate);
 			userData.lastonlineISO = utils.toISOString(userData.lastonline || userData.joindate);
 			userData.age = Math.max(0, userData.birthday ? Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000) : 0);
 
-			if (!(isAdmin || isGlobalModerator || self || (userData.email && userSettings.showemail))) {
+			userData.emailClass = 'hide';
+
+			if (!(isAdmin || isGlobalModerator || isSelf || (userData.email && userSettings.showemail))) {
 				userData.email = '';
+			} else if (!userSettings.showemail) {
+				userData.emailClass = '';
 			}
 
-			userData.emailClass = (self && !userSettings.showemail) ? '' : 'hide';
-
-			if (!isAdmin && !isGlobalModerator && !self && !userSettings.showfullname) {
+			if (!isAdmin && !isGlobalModerator && !isSelf && !userSettings.showfullname) {
 				userData.fullname = '';
 			}
 
-			if (isAdmin || isGlobalModerator || self) {
+			if (isAdmin || isGlobalModerator || isSelf) {
 				userData.ips = results.ips;
 			}
 
@@ -84,13 +86,15 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
 			userData.isAdmin = isAdmin;
 			userData.isGlobalModerator = isGlobalModerator;
 			userData.canBan = isAdmin || isGlobalModerator;
-			userData.canChangePassword = isAdmin || self;
-			userData.isSelf = self;
-			userData.showHidden = self || isAdmin || isGlobalModerator;
+			userData.canChangePassword = isAdmin || (isSelf && parseInt(meta.config['password:disableEdit'], 10) !== 1);
+			userData.isSelf = isSelf;
+			userData.showHidden = isSelf || isAdmin || isGlobalModerator;
 			userData.groups = Array.isArray(results.groups) && results.groups.length ? results.groups[0] : [];
 			userData.disableSignatures = meta.config.disableSignatures !== undefined && parseInt(meta.config.disableSignatures, 10) === 1;
+			userData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
+			userData['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
 			userData['email:confirmed'] = !!parseInt(userData['email:confirmed'], 10);
-			userData.profile_links = filterLinks(results.profile_links, self);
+			userData.profile_links = filterLinks(results.profile_links, isSelf);
 			userData.sso = results.sso.associations;
 			userData.status = user.getStatus(userData);
 			userData.banned = parseInt(userData.banned, 10) === 1;
@@ -100,7 +104,6 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
 			userData.followingCount = parseInt(userData.followingCount, 10) || 0;
 			userData.followerCount = parseInt(userData.followerCount, 10) || 0;
 
-			userData.username = validator.escape(userData.username || '');
 			userData.email = validator.escape(userData.email || '');
 			userData.fullname = validator.escape(userData.fullname || '');
 			userData.location = validator.escape(userData.location || '');
@@ -155,6 +158,8 @@ helpers.getBaseUser = function(userslug, callerUID, callback) {
 			results.user.showHidden = results.user.isSelf || results.isAdmin || results.isGlobalModerator;
 			results.user.profile_links = filterLinks(results.profile_links, results.user.isSelf);
 
+			results.user['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
+			results.user['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
 			results.user['cover:url'] = results.user['cover:url'] || require('../../coverPhoto').getDefaultProfileCover(results.user.uid);
 			results.user['cover:position'] = results.user['cover:position'] || '50% 50%';
 
@@ -169,4 +174,4 @@ function filterLinks(links, self) {
 	});
 }
 
-module.exports = helpers;
\ No newline at end of file
+module.exports = helpers;
diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js
index d734ebe142..e52b5f4861 100644
--- a/src/controllers/accounts/profile.js
+++ b/src/controllers/accounts/profile.js
@@ -1,15 +1,15 @@
 'use strict';
 
-var nconf = require('nconf'),
-	async = require('async'),
-	S = require('string'),
-
-	user = require('../../user'),
-	posts = require('../../posts'),
-	plugins = require('../../plugins'),
-	meta = require('../../meta'),
-	accountHelpers = require('./helpers'),
-	helpers = require('../helpers');
+var nconf = require('nconf');
+var async = require('async');
+var S = require('string');
+
+var user = require('../../user');
+var posts = require('../../posts');
+var plugins = require('../../plugins');
+var meta = require('../../meta');
+var accountHelpers = require('./helpers');
+var helpers = require('../helpers');
 
 
 var profileController = {};
diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js
index 7028972491..b5b020c118 100644
--- a/src/controllers/accounts/settings.js
+++ b/src/controllers/accounts/settings.js
@@ -33,7 +33,7 @@ settingsController.get = function(req, res, callback) {
 					user.getSettings(userData.uid, next);
 				},
 				userGroups: function(next) {
-					groups.getUserGroups([userData.uid], next);
+					groups.getUserGroupsFromSet('groups:createtime', [userData.uid], next);
 				},
 				languages: function(next) {
 					languages.list(next);
@@ -42,14 +42,16 @@ settingsController.get = function(req, res, callback) {
 					getHomePageRoutes(next);
 				},
 				ips: function (next) {
-					user.getIPs(req.uid, 4, next);
+					user.getIPs(userData.uid, 4, next);
 				},
 				sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID)
 			}, next);
 		},
 		function(results, next) {
 			userData.settings = results.settings;
-			userData.userGroups = results.userGroups[0];
+			userData.userGroups = results.userGroups[0].filter(function(group) {
+				return group && group.userTitleEnabled && !groups.isPrivilegeGroup(group.name) && group.name !== 'registered-users';
+			});
 			userData.languages = results.languages;
 			userData.homePageRoutes = results.homePageRoutes;
 			userData.ips = results.ips;
diff --git a/src/controllers/admin.js b/src/controllers/admin.js
index 8b9b1cafc7..2bba60cae6 100644
--- a/src/controllers/admin.js
+++ b/src/controllers/admin.js
@@ -5,6 +5,7 @@ var adminController = {
 	categories: require('./admin/categories'),
 	tags: require('./admin/tags'),
 	flags: require('./admin/flags'),
+	blacklist: require('./admin/blacklist'),
 	groups: require('./admin/groups'),
 	appearance: require('./admin/appearance'),
 	extend: {
diff --git a/src/controllers/admin/blacklist.js b/src/controllers/admin/blacklist.js
new file mode 100644
index 0000000000..2c0104f742
--- /dev/null
+++ b/src/controllers/admin/blacklist.js
@@ -0,0 +1,16 @@
+"use strict";
+
+var meta = require('../../meta');
+
+var blacklistController = {};
+
+blacklistController.get = function(req, res, next) {
+	meta.blacklist.get(function(err, rules) {
+		if (err) {
+			return next(err);
+		}
+		res.render('admin/manage/ip-blacklist', {rules: rules, title: 'IP Blacklist'});
+	});
+};
+
+module.exports = blacklistController;
diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js
index 704dc21a2c..5444a087ea 100644
--- a/src/controllers/admin/categories.js
+++ b/src/controllers/admin/categories.js
@@ -1,11 +1,12 @@
 "use strict";
 
-var async = require('async'),
-	
-	categories = require('../../categories'),
-	privileges = require('../../privileges'),
-	analytics = require('../../analytics'),
-	plugins = require('../../plugins');
+var async = require('async');
+
+var categories = require('../../categories');
+var privileges = require('../../privileges');
+var analytics = require('../../analytics');
+var plugins = require('../../plugins');
+var translator = require('../../../public/src/modules/translator')
 
 
 var categoriesController = {};
@@ -13,31 +14,38 @@ var categoriesController = {};
 categoriesController.get = function(req, res, next) {
 	async.parallel({
 		category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
-		privileges: async.apply(privileges.categories.list, req.params.category_id),
-		analytics: async.apply(analytics.getCategoryAnalytics, req.params.category_id)
+		privileges: async.apply(privileges.categories.list, req.params.category_id)
 	}, function(err, data) {
 		if (err) {
 			return next(err);
 		}
 
-		plugins.fireHook('filter:admin.category.get', { req: req, res: res, category: data.category[0], privileges: data.privileges, analytics: data.analytics }, function(err, data) {
+		plugins.fireHook('filter:admin.category.get', { req: req, res: res, category: data.category[0], privileges: data.privileges }, function(err, data) {
 			if (err) {
 				return next(err);
 			}
-
+			data.category.name = translator.escape(data.category.name);
 			res.render('admin/manage/category', {
 				category: data.category,
-				privileges: data.privileges,
-				analytics: data.analytics
+				privileges: data.privileges
 			});
 		});
 	});
 };
 
 categoriesController.getAll = function(req, res, next) {
-	//Categories list will be rendered on client side with recursion, etc.
+	// Categories list will be rendered on client side with recursion, etc.
 	res.render('admin/manage/categories', {});
 };
 
+categoriesController.getAnalytics = function(req, res, next) {
+	async.parallel({
+		name: async.apply(categories.getCategoryField, req.params.category_id, 'name'),
+		analytics: async.apply(analytics.getCategoryAnalytics, req.params.category_id)
+	}, function(err, data) {
+		res.render('admin/manage/category-analytics', data);
+	});
+};
+
 
 module.exports = categoriesController;
diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js
index 565d23e563..24a65983f1 100644
--- a/src/controllers/admin/dashboard.js
+++ b/src/controllers/admin/dashboard.js
@@ -81,13 +81,13 @@ function getStatsForSet(set, field, callback) {
 	var now = Date.now();
 	async.parallel({
 		day: function(next) {
-			db.sortedSetCount(set, now - terms.day, now, next);
+			db.sortedSetCount(set, now - terms.day, '+inf', next);
 		},
 		week: function(next) {
-			db.sortedSetCount(set, now - terms.week, now, next);
+			db.sortedSetCount(set, now - terms.week, '+inf', next);
 		},
 		month: function(next) {
-			db.sortedSetCount(set, now - terms.month, now, next);
+			db.sortedSetCount(set, now - terms.month, '+inf', next);
 		},
 		alltime: function(next) {
 			getGlobalField(field, next);
diff --git a/src/controllers/admin/groups.js b/src/controllers/admin/groups.js
index dc8bf6ff82..03b3514327 100644
--- a/src/controllers/admin/groups.js
+++ b/src/controllers/admin/groups.js
@@ -66,7 +66,7 @@ groupsController.get = function(req, res, callback) {
 			return callback(err);
 		}
 		group.isOwner = true;
-		res.render('admin/manage/group', {group: group});
+		res.render('admin/manage/group', {group: group, allowPrivateGroups: parseInt(meta.config.allowPrivateGroups, 10) === 1});
 	});
 };
 
diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js
index ec8b79e0a3..2459ad7140 100644
--- a/src/controllers/admin/info.js
+++ b/src/controllers/admin/info.js
@@ -2,24 +2,52 @@
 
 var async = require('async');
 var os = require('os');
+var winston = require('winston');
 var nconf = require('nconf');
 var exec = require('child_process').exec;
 
+var pubsub = require('../../pubsub');
 var rooms = require('../../socket.io/admin/rooms');
 
 var infoController = {};
 
+var info = {};
+
 infoController.get = function(req, res, next) {
+	info = {};
+	pubsub.publish('sync:node:info:start');
+	setTimeout(function() {
+		var data = [];
+		Object.keys(info).forEach(function(key) {
+			data.push(info[key]);
+		});
+		data.sort(function(a, b) {
+			return (a.os.hostname < b.os.hostname) ? -1 : (a.os.hostname > b.os.hostname) ? 1 : 0;
+		});
+		res.render('admin/development/info', {info: data, infoJSON: JSON.stringify(data, null, 4), host: os.hostname(), port: nconf.get('port')});
+	}, 300);
+};
+
+pubsub.on('sync:node:info:start', function() {
+	getNodeInfo(function(err, data) {
+		if (err) {
+			return winston.error(err);
+		}
+		pubsub.publish('sync:node:info:end', {data: data, id: os.hostname() + ':' + nconf.get('port')});
+	});
+});
 
+pubsub.on('sync:node:info:end', function(data) {
+	info[data.id] = data.data;
+});
+
+function getNodeInfo(callback) {
 	var data = {
 		process: {
 			port: nconf.get('port'),
 			pid: process.pid,
 			title: process.title,
-			arch: process.arch,
-			platform: process.platform,
 			version: process.version,
-			versions: process.versions,
 			memoryUsage: process.memoryUsage(),
 			uptime: process.uptime()
 		},
@@ -28,19 +56,28 @@ infoController.get = function(req, res, next) {
 			type: os.type(),
 			platform: os.platform(),
 			arch: os.arch(),
-			release: os.release()
+			release: os.release(),
+			load: os.loadavg().map(function(load){ return load.toFixed(2); }).join(', ')
 		}
 	};
 
-	getGitInfo(function(err, gitInfo) {
+	async.parallel({
+		pubsub: function(next) {
+			pubsub.publish('sync:stats:start');
+			next();
+		},
+		gitInfo: function(next) {
+			getGitInfo(next);
+		}
+	}, function(err, results) {
 		if (err) {
-			return next(err);
+			return callback(err);
 		}
-		data.git = gitInfo;
-
-		res.render('admin/development/info', {info: JSON.stringify(data, null, 4), stats: JSON.stringify(rooms.getStats(), null, 4)});
+		data.git = results.gitInfo;
+		data.stats = rooms.stats[data.os.hostname + ':' + data.process.port];
+		callback(null, data);
 	});
-};
+}
 
 function getGitInfo(callback) {
 	function get(cmd,  callback) {
diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js
index 5f582bc117..aea79e3934 100644
--- a/src/controllers/admin/uploads.js
+++ b/src/controllers/admin/uploads.js
@@ -160,7 +160,6 @@ function uploadImage(filename, folder, uploadedFile, req, res, next) {
 		}
 
 		res.json([{name: uploadedFile.name, url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
-		next();
 	}
 
 	if (plugins.hasListeners('filter:uploadImage')) {
diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js
index c68fbb8d09..7e5bd530d6 100644
--- a/src/controllers/admin/users.js
+++ b/src/controllers/admin/users.js
@@ -31,7 +31,7 @@ usersController.noPosts = function(req, res, next) {
 usersController.inactive = function(req, res, next) {
 	var timeRange = 1000 * 60 * 60 * 24 * 30 * (parseInt(req.query.months, 10) || 3);
 	var cutoff = Date.now() - timeRange;
-	getUsersByScore('users:online', 'inactive', 0, cutoff, req, res, next);
+	getUsersByScore('users:online', 'inactive', '-inf', cutoff, req, res, next);
 };
 
 function getUsersByScore(set, section, min, max, req, res, callback) {
@@ -77,10 +77,17 @@ usersController.banned = function(req, res, next) {
 };
 
 usersController.registrationQueue = function(req, res, next) {
+	var page = parseInt(req.query.page, 10) || 1;
+	var itemsPerPage = 20;
+	var start = (page - 1) * 20;
+	var stop = start + itemsPerPage - 1;
 	var invitations;
 	async.parallel({
+		registrationQueueCount: function(next) {
+			db.sortedSetCard('registration:queue', next);
+		},
 		users: function(next) {
-			user.getRegistrationQueue(0, -1, next);
+			user.getRegistrationQueue(start, stop, next);
 		},
 		invites: function(next) {
 			async.waterfall([
@@ -118,6 +125,8 @@ usersController.registrationQueue = function(req, res, next) {
 		if (err) {
 			return next(err);
 		}
+		var pageCount = Math.max(1, Math.ceil(data.registrationQueueCount / itemsPerPage));
+		data.pagination = pagination.create(page, pageCount);
 		res.render('admin/manage/registration', data);
 	});
 };
@@ -146,7 +155,7 @@ function getUsers(set, section, req, res, next) {
 		var data = {
 			users: results.users,
 			page: page,
-			pageCount: Math.ceil(results.count / resultsPerPage)
+			pageCount: Math.max(1, Math.ceil(results.count / resultsPerPage))
 		};
 		data[section] = true;
 		render(req, res, data);
diff --git a/src/controllers/api.js b/src/controllers/api.js
index fb5ab709b6..77ae7131ad 100644
--- a/src/controllers/api.js
+++ b/src/controllers/api.js
@@ -1,32 +1,21 @@
 "use strict";
 
-var async = require('async'),
-	validator = require('validator'),
-	nconf = require('nconf'),
-
-	meta = require('../meta'),
-	user = require('../user'),
-	posts = require('../posts'),
-	topics = require('../topics'),
-	categories = require('../categories'),
-	privileges = require('../privileges'),
-	plugins = require('../plugins'),
-	helpers = require('./helpers'),
-	widgets = require('../widgets');
+var async = require('async');
+var validator = require('validator');
+var nconf = require('nconf');
+
+var meta = require('../meta');
+var user = require('../user');
+var posts = require('../posts');
+var topics = require('../topics');
+var categories = require('../categories');
+var privileges = require('../privileges');
+var plugins = require('../plugins');
+var widgets = require('../widgets');
 
 var apiController = {};
 
 apiController.getConfig = function(req, res, next) {
-	function filterConfig() {
-		plugins.fireHook('filter:config.get', config, function(err, config) {
-			if (res.locals.isAPI) {
-				res.status(200).json(config);
-			} else {
-				next(err, config);
-			}
-		});
-	}
-
 	var config = {};
 	config.environment = process.env.NODE_ENV;
 	config.relative_path = nconf.get('relative_path');
@@ -51,7 +40,6 @@ apiController.getConfig = function(req, res, next) {
 	config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1;
 	config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1;
 	config.usePagination = parseInt(meta.config.usePagination, 10) === 1;
-	config.disableSocialButtons = parseInt(meta.config.disableSocialButtons, 10) === 1;
 	config.disableChat = parseInt(meta.config.disableChat, 10) === 1;
 	config.socketioTransports = nconf.get('socket.io:transports') || ['polling', 'websocket'];
 	config.websocketAddress = nconf.get('socket.io:address') || '';
@@ -73,27 +61,41 @@ apiController.getConfig = function(req, res, next) {
 	config.searchEnabled = plugins.hasListeners('filter:search.query');
 	config.bootswatchSkin = 'default';
 
-	if (!req.user) {
-		return filterConfig();
-	}
-
-	user.getSettings(req.user.uid, function(err, settings) {
+	async.waterfall([
+		function (next) {
+			if (!req.user) {
+				return next(null, config);
+			}
+			user.getSettings(req.uid, function(err, settings) {
+				if (err) {
+					return next(err);
+				}
+				config.usePagination = settings.usePagination;
+				config.topicsPerPage = settings.topicsPerPage;
+				config.postsPerPage = settings.postsPerPage;
+				config.notificationSounds = settings.notificationSounds;
+				config.userLang = req.query.lang || settings.userLang || config.defaultLang;
+				config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab;
+				config.topicPostSort = settings.topicPostSort || config.topicPostSort;
+				config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort;
+				config.topicSearchEnabled = settings.topicSearchEnabled || false;
+				config.bootswatchSkin = settings.bootswatchSkin || config.bootswatchSkin;
+				next(null, config);
+			});
+		},
+		function (config, next) {
+			plugins.fireHook('filter:config.get', config, next);
+		}
+	], function(err, config) {
 		if (err) {
 			return next(err);
 		}
 
-		config.usePagination = settings.usePagination;
-		config.topicsPerPage = settings.topicsPerPage;
-		config.postsPerPage = settings.postsPerPage;
-		config.notificationSounds = settings.notificationSounds;
-		config.userLang = req.query.lang || settings.userLang || config.defaultLang;
-		config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab;
-		config.topicPostSort = settings.topicPostSort || config.topicPostSort;
-		config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort;
-		config.topicSearchEnabled = settings.topicSearchEnabled || false;
-		config.bootswatchSkin = settings.bootswatchSkin || config.bootswatchSkin;
-
-		filterConfig();
+		if (res.locals.isAPI) {
+			res.json(config);
+		} else {
+			next(null, config);
+		}
 	});
 };
 
@@ -126,6 +128,16 @@ apiController.renderWidgets = function(req, res, next) {
 };
 
 apiController.getObject = function(req, res, next) {
+	apiController.getObjectByType(req.uid, req.params.type, req.params.id, function(err, results) {
+		if (err) {
+			return next(err);
+		}
+
+		res.json(results);
+	});
+};
+
+apiController.getObjectByType = function(uid, type, id, callback) {
 	var methods = {
 		post: {
 			canRead: privileges.posts.can,
@@ -141,74 +153,101 @@ apiController.getObject = function(req, res, next) {
 		}
 	};
 
-	if (!methods[req.params.type]) {
-		return next();
+	if (!methods[type]) {
+		return callback();
 	}
 
-	async.parallel({
-		canRead: async.apply(methods[req.params.type].canRead, 'read', req.params.id, req.uid),
-		data: async.apply(methods[req.params.type].data, req.params.id)
-	}, function(err, results) {
-		if (err || !results.data) {
-			return next(err);
-		}
-
-		if (!results.canRead) {
-			return helpers.notAllowed(req, res);
+	async.waterfall([
+		function (next) {
+			methods[type].canRead('read', id, uid, next);
+		},
+		function (canRead, next) {
+			if (!canRead) {
+				return next(new Error('[[error:no-privileges]]'));
+			}
+			methods[type].data(id, next);
 		}
-
-		res.json(results.data);
-	});
+	], callback);
 };
 
-
 apiController.getUserByUID = function(req, res, next) {
 	var uid = req.params.uid ? req.params.uid : 0;
 
-	getUserByUID(uid, res, next);
+	apiController.getUserDataByUID(req.uid, uid, function(err, data) {
+		if (err) {
+			return next(err);
+		}
+		res.json(data);
+	});
 };
 
 apiController.getUserByUsername = function(req, res, next) {
 	var username = req.params.username ? req.params.username : 0;
 
+	apiController.getUserDataByUsername(req.uid, username, function(err, data) {
+		if (err) {
+			return next(err);
+		}
+		res.json(data);
+	});
+};
+
+apiController.getUserByEmail = function(req, res, next) {
+	var email = req.params.email ? req.params.email : 0;
+
+	apiController.getUserDataByEmail(req.uid, email, function(err, data) {
+		if (err) {
+			return next(err);
+		}
+		res.json(data);
+	});
+};
+
+apiController.getUserDataByUsername = function(callerUid, username, callback) {
 	async.waterfall([
 		function(next) {
 			user.getUidByUsername(username, next);
 		},
 		function(uid, next) {
-			getUserByUID(uid, res, next);
+			apiController.getUserDataByUID(callerUid, uid, next);
 		}
-	], next);
+	], callback);
 };
 
-apiController.getUserByEmail = function(req, res, next) {
-	var email = req.params.email ? req.params.email : 0;
-
+apiController.getUserDataByEmail = function(callerUid, email, callback) {
 	async.waterfall([
 		function(next) {
 			user.getUidByEmail(email, next);
 		},
 		function(uid, next) {
-			getUserByUID(uid, res, next);
+			apiController.getUserDataByUID(callerUid, uid, next);
 		}
-	], next);
+	], callback);
 };
 
-function getUserByUID(uid, res, next) {
+apiController.getUserDataByUID = function(callerUid, uid, callback) {
+	if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) {
+		return callback(new Error('[[error:no-privileges]]'));
+	}
+
+	if (!parseInt(uid, 10)) {
+		return callback(new Error('[[error:no-user]]'));
+	}
+
 	async.parallel({
 		userData: async.apply(user.getUserData, uid),
 		settings: async.apply(user.getSettings, uid)
 	}, function(err, results) {
 		if (err || !results.userData) {
-			return next(err);
+			return callback(err || new Error('[[error:no-user]]'));
 		}
 
 		results.userData.email = results.settings.showemail ? results.userData.email : undefined;
 		results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined;
 
-		res.json(results.userData);
+		callback(null, results.userData);
 	});
-}
+};
 
 apiController.getModerators = function(req, res, next) {
 	categories.getModerators(req.params.cid, function(err, moderators) {
diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js
index fa6cc6fd8c..8db2c9e580 100644
--- a/src/controllers/authentication.js
+++ b/src/controllers/authentication.js
@@ -1,20 +1,20 @@
 "use strict";
 
-var async = require('async'),
-	winston = require('winston'),
-	passport = require('passport'),
-	nconf = require('nconf'),
-	validator = require('validator'),
-	_ = require('underscore'),
-
-	db = require('../database'),
-	meta = require('../meta'),
-	user = require('../user'),
-	plugins = require('../plugins'),
-	utils = require('../../public/src/utils'),
-	Password = require('../password'),
-
-	authenticationController = {};
+var async = require('async');
+var winston = require('winston');
+var passport = require('passport');
+var nconf = require('nconf');
+var validator = require('validator');
+var _ = require('underscore');
+
+var db = require('../database');
+var meta = require('../meta');
+var user = require('../user');
+var plugins = require('../plugins');
+var utils = require('../../public/src/utils');
+var Password = require('../password');
+
+var authenticationController = {};
 
 authenticationController.register = function(req, res, next) {
 	var registrationType = meta.config.registrationType || 'normal';
@@ -86,14 +86,14 @@ function registerAndLoginUser(req, res, userData, callback) {
 		},
 		function(_uid, next) {
 			uid = _uid;
-			if (res.locals.processLogin === true) {
-				doLogin(req, uid, next);
+			if (res.locals.processLogin) {
+				authenticationController.doLogin(req, uid, next);
 			} else {
 				next();
 			}
 		},
 		function(next) {
-			user.deleteInvitation(userData.email);
+			user.deleteInvitationKey(userData.email);
 			plugins.fireHook('filter:register.complete', {uid: uid, referrer: req.body.referrer || nconf.get('relative_path') + '/'}, next);
 		}
 	], callback);
@@ -171,7 +171,7 @@ function continueLogin(req, res, next) {
 				res.status(200).send(nconf.get('relative_path') + '/reset/' + code);
 			});
 		} else {
-			doLogin(req, userData.uid, function(err) {
+			authenticationController.doLogin(req, userData.uid, function(err) {
 				if (err) {
 					return res.status(403).send(err.message);
 				}
@@ -189,39 +189,54 @@ function continueLogin(req, res, next) {
 	})(req, res, next);
 }
 
-function doLogin(req, uid, callback) {
+authenticationController.doLogin = function(req, uid, callback) {
+	if (!uid) {
+		return callback();
+	}
+
 	req.login({uid: uid}, function(err) {
 		if (err) {
 			return callback(err);
 		}
 
-		if (uid) {
-			var uuid = utils.generateUUID();
-			req.session.meta = {};
-
-			// Associate IP used during login with user account
-			user.logIP(uid, req.ip);
-			req.session.meta.ip = req.ip;
-
-			// Associate metadata retrieved via user-agent
-			req.session.meta = _.extend(req.session.meta, {
-				uuid: uuid,
-				datetime: Date.now(),
-				platform: req.useragent.platform,
-				browser: req.useragent.browser,
-				version: req.useragent.version
-			});
+		authenticationController.onSuccessfulLogin(req, uid, callback);
+	});
+};
 
-			// Associate login session with user
-			user.auth.addSession(uid, req.sessionID);
-			db.setObjectField('uid:' + uid + 'sessionUUID:sessionId', uuid, req.sessionID);
+authenticationController.onSuccessfulLogin = function(req, uid, callback) {
+	callback = callback || function() {};
+	var uuid = utils.generateUUID();
+	req.session.meta = {};
+
+	// Associate IP used during login with user account
+	user.logIP(uid, req.ip);
+	req.session.meta.ip = req.ip;
+
+	// Associate metadata retrieved via user-agent
+	req.session.meta = _.extend(req.session.meta, {
+		uuid: uuid,
+		datetime: Date.now(),
+		platform: req.useragent.platform,
+		browser: req.useragent.browser,
+		version: req.useragent.version
+	});
 
-			plugins.fireHook('action:user.loggedIn', uid);
+	// Associate login session with user
+	async.parallel([
+		function (next) {
+			user.auth.addSession(uid, req.sessionID, next);
+		},
+		function (next) {
+			db.setObjectField('uid:' + uid + 'sessionUUID:sessionId', uuid, req.sessionID, next);
 		}
-
+	], function(err) {
+		if (err) {
+			return callback(err);
+		}
+		plugins.fireHook('action:user.loggedIn', uid);
 		callback();
 	});
-}
+};
 
 authenticationController.localLogin = function(req, username, password, next) {
 	if (!username) {
diff --git a/src/controllers/categories.js b/src/controllers/categories.js
index 28a4f92192..7ed087c704 100644
--- a/src/controllers/categories.js
+++ b/src/controllers/categories.js
@@ -66,7 +66,7 @@ categoriesController.list = function(req, res, next) {
 				if (category && Array.isArray(category.posts) && category.posts.length) {
 					category.teaser = {
 						url: nconf.get('relative_path') + '/topic/' + category.posts[0].topic.slug + '/' + category.posts[0].index,
-						timestampISO: category.posts[0].timestamp
+						timestampISO: category.posts[0].timestampISO
 					};
 				}
 			});
diff --git a/src/controllers/category.js b/src/controllers/category.js
index 9a31f8d916..f80ad122a7 100644
--- a/src/controllers/category.js
+++ b/src/controllers/category.js
@@ -51,8 +51,8 @@ categoryController.get = function(req, res, callback) {
 				return helpers.notAllowed(req, res);
 			}
 
-			if ((!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) {
-				return helpers.redirect(res, '/category/' + encodeURI(results.categoryData.slug));
+			if (!res.locals.isAPI && (!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) {
+				return helpers.redirect(res, '/category/' + results.categoryData.slug);
 			}
 
 			var settings = results.userSettings;
@@ -196,7 +196,6 @@ categoryController.get = function(req, res, callback) {
 			});
 
 			plugins.fireHook('filter:category.build', {req: req, res: res, templateData: categoryData}, next);
-			next(null, categoryData);
 		}
 	], function (err, data) {
 		if (err) {
diff --git a/src/controllers/globalmods.js b/src/controllers/globalmods.js
new file mode 100644
index 0000000000..3275c7929e
--- /dev/null
+++ b/src/controllers/globalmods.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var user = require('../user');
+var adminFlagsController = require('./admin/flags');
+var adminBlacklistController = require('./admin/blacklist');
+
+var globalModsController = {};
+
+globalModsController.flagged = function(req, res, next) {
+	user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) {
+		if (err || !isAdminOrGlobalMod) {
+			return next(err);
+		}
+
+		adminFlagsController.get(req, res, next);
+	});
+};
+
+globalModsController.ipBlacklist = function(req, res, next) {
+	user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) {
+		if (err || !isAdminOrGlobalMod) {
+			return next(err);
+		}
+
+		adminBlacklistController.get(req, res, next);
+	});
+};
+
+module.exports = globalModsController;
diff --git a/src/controllers/groups.js b/src/controllers/groups.js
index c77c2c61ee..837ba3b1f6 100644
--- a/src/controllers/groups.js
+++ b/src/controllers/groups.js
@@ -90,6 +90,7 @@ groupsController.details = function(req, res, callback) {
 			}
 			results.title = '[[pages:group, ' + results.group.displayName + ']]';
 			results.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[pages:groups]]', url: '/groups' }, {text: results.group.displayName}]);
+			results.allowPrivateGroups = parseInt(meta.config.allowPrivateGroups, 10) === 1;
 			plugins.fireHook('filter:group.build', {req: req, res: res, templateData: results}, next);
 		}
 	], function(err, results) {
@@ -112,7 +113,7 @@ groupsController.members = function(req, res, next) {
 			user.getUsersFromSet('group:' + groupName + ':members', req.uid, 0, 49, next);
 		},
 	], function(err, users) {
-		if (err) {
+		if (err || !groupName) {
 			return next(err);
 		}
 
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index 94dfe022c2..058a1849b1 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -41,7 +41,7 @@ helpers.redirect = function(res, url) {
 	if (res.locals.isAPI) {
 		res.status(308).json(url);
 	} else {
-		res.redirect(nconf.get('relative_path') + url);
+		res.redirect(nconf.get('relative_path') + encodeURI(url));
 	}
 };
 
@@ -105,8 +105,12 @@ helpers.buildTitle = function(pageTitle) {
 
 	var browserTitle = validator.escape(meta.config.browserTitle || meta.config.title || 'NodeBB');
 	pageTitle = pageTitle || '';
-	var title = titleLayout.replace('{pageTitle}', pageTitle).replace('{browserTitle}', browserTitle);
+	var title = titleLayout.replace('{pageTitle}', function() {
+		return pageTitle;
+	}).replace('{browserTitle}', function() {
+		return browserTitle;
+	});
 	return title;
 };
 
-module.exports = helpers;
\ No newline at end of file
+module.exports = helpers;
diff --git a/src/controllers/index.js b/src/controllers/index.js
index cc83f0727a..fa35523c2b 100644
--- a/src/controllers/index.js
+++ b/src/controllers/index.js
@@ -12,7 +12,6 @@ var helpers = require('./helpers');
 
 var Controllers = {
 	topics: require('./topics'),
-	posts: require('./posts'),
 	categories: require('./categories'),
 	category: require('./category'),
 	unread: require('./unread'),
@@ -25,7 +24,8 @@ var Controllers = {
 	accounts: require('./accounts'),
 	authentication: require('./authentication'),
 	api: require('./api'),
-	admin: require('./admin')
+	admin: require('./admin'),
+	globalMods: require('./globalmods')
 };
 
 
@@ -36,7 +36,7 @@ Controllers.home = function(req, res, next) {
 		if (err) {
 			return next(err);
 		}
-		if (settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
+		if (parseInt(meta.config.allowUserHomePage, 10) === 1 && settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
 			route = settings.homePageRoute || route;
 		}
 
diff --git a/src/controllers/posts.js b/src/controllers/posts.js
deleted file mode 100644
index 5618069b9b..0000000000
--- a/src/controllers/posts.js
+++ /dev/null
@@ -1,19 +0,0 @@
-"use strict";
-
-var user = require('../user');
-var adminFlagsController = require('./admin/flags');
-
-var postsController = {};
-
-postsController.flagged = function(req, res, next) {
-	user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) {
-		if (err || !isAdminOrGlobalMod) {
-			return next(err);
-		}
-
-		adminFlagsController.get(req, res, next);
-	});
-};
-
-
-module.exports = postsController;
diff --git a/src/controllers/tags.js b/src/controllers/tags.js
index 60f4c1d028..af8f6058c0 100644
--- a/src/controllers/tags.js
+++ b/src/controllers/tags.js
@@ -65,6 +65,9 @@ tagsController.getTags = function(req, res, next) {
 		if (err) {
 			return next(err);
 		}
+		tags = tags.filter(function(tag) {
+			return tag && tag.score > 0;
+		});
 		var data = {
 			tags: tags,
 			nextStart: 100,
diff --git a/src/controllers/topics.js b/src/controllers/topics.js
index 5c3728a428..c3c2847778 100644
--- a/src/controllers/topics.js
+++ b/src/controllers/topics.js
@@ -4,7 +4,6 @@
 var async = require('async');
 var S = require('string');
 var nconf = require('nconf');
-var validator = require('validator');
 
 var user = require('../user');
 var meta = require('../meta');
@@ -24,6 +23,7 @@ topicsController.get = function(req, res, callback) {
 	var currentPage = parseInt(req.query.page, 10) || 1;
 	var pageCount = 1;
 	var userPrivileges;
+	var settings;
 
 	if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) {
 		return callback();
@@ -54,11 +54,15 @@ topicsController.get = function(req, res, callback) {
 				return helpers.notAllowed(req, res);
 			}
 
-			if ((!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) {
-				return helpers.redirect(res, '/topic/' + encodeURI(results.topic.slug));
+			if (!res.locals.isAPI && (!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) {
+				var url = '/topic/' + results.topic.slug;
+				if (req.params.post_index){
+					url += '/'+req.params.post_index;
+				}
+				return helpers.redirect(res, url);
 			}
 
-			var settings = results.settings;
+			settings = results.settings;
 			var postCount = parseInt(results.topic.postcount, 10);
 			pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage));
 
@@ -120,7 +124,7 @@ topicsController.get = function(req, res, callback) {
 				return callback();
 			}
 
-			topics.modifyPostsByPrivilege(topicData.posts, userPrivileges);
+			topics.modifyPostsByPrivilege(topicData, userPrivileges);
 
 			plugins.fireHook('filter:controllers.topic.get', {topicData: topicData, uid: req.uid}, next);
 		},
@@ -183,7 +187,7 @@ topicsController.get = function(req, res, callback) {
 			res.locals.metaTags = [
 				{
 					name: "title",
-					content: topicData.title
+					content: topicData.titleRaw
 				},
 				{
 					name: "description",
@@ -191,7 +195,7 @@ topicsController.get = function(req, res, callback) {
 				},
 				{
 					property: 'og:title',
-					content: topicData.title
+					content: topicData.titleRaw
 				},
 				{
 					property: 'og:description',
@@ -257,6 +261,7 @@ topicsController.get = function(req, res, callback) {
 		data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
 		data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
 		data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
+		data.scrollToMyPost = settings.scrollToMyPost;
 		data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss';
 		data.pagination = pagination.create(currentPage, pageCount);
 		data.pagination.rel.forEach(function(rel) {
diff --git a/src/controllers/unread.js b/src/controllers/unread.js
index 741e5c673a..d81774661f 100644
--- a/src/controllers/unread.js
+++ b/src/controllers/unread.js
@@ -1,14 +1,14 @@
 
 'use strict';
 
-var async = require('async'),
-
-	meta = require('../meta'),
-	categories = require('../categories'),
-	privileges = require('../privileges'),
-	user = require('../user'),
-	topics = require('../topics'),
-	helpers = require('./helpers');
+var async = require('async');
+var meta = require('../meta');
+var categories = require('../categories');
+var privileges = require('../privileges');
+var user = require('../user')
+var topics = require('../topics');
+var helpers = require('./helpers');
+var plugins = require('../plugins');
 
 var unreadController = {};
 
@@ -47,17 +47,18 @@ unreadController.get = function(req, res, next) {
 				}
 			});
 			results.unreadTopics.categories = categories;
-			next(null, results.unreadTopics);
+
+			results.unreadTopics.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[unread:title]]'}]);
+			results.unreadTopics.title = '[[pages:unread]]';
+
+			plugins.fireHook('filter:unread.build', {req: req, res: res, templateData: results.unreadTopics}, next);
 		}
 	], function(err, data) {
 		if (err) {
 			return next(err);
 		}
 
-		data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[unread:title]]'}]);
-		data.title = '[[pages:unread]]';
-
-		res.render('unread', data);
+		res.render('unread', data.templateData);
 	});
 };
 
@@ -72,4 +73,4 @@ unreadController.unreadTotal = function(req, res, next) {
 	});
 };
 
-module.exports = unreadController;
\ No newline at end of file
+module.exports = unreadController;
diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js
index d455eaac2f..59253c5666 100644
--- a/src/controllers/uploads.js
+++ b/src/controllers/uploads.js
@@ -45,17 +45,26 @@ uploadsController.upload = function(req, res, filesIterator, next) {
 
 uploadsController.uploadPost = function(req, res, next) {
 	uploadsController.upload(req, res, function(uploadedFile, next) {
-		if (uploadedFile.type.match(/image./)) {
-			file.isFileTypeAllowed(uploadedFile.path, function(err, tempPath) {
-				if (err) {
-					return next(err);
-				}
-
-				uploadImage(req.user ? req.user.uid : 0, uploadedFile, next);
-			});
-		} else {
-			uploadFile(req.user ? req.user.uid : 0, uploadedFile, next);
+		var isImage = uploadedFile.type.match(/image./);
+		if (isImage && plugins.hasListeners('filter:uploadImage')) {
+			return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, next);
 		}
+
+		async.waterfall([
+			function(next) {
+				if (isImage) {
+					file.isFileTypeAllowed(uploadedFile.path, next);
+				} else {
+					next();
+				}
+			},
+			function (next) {
+				if (parseInt(meta.config.allowFileUploads, 10) !== 1) {
+					return next(new Error('[[error:uploads-are-disabled]]'));
+				}
+				uploadFile(req.uid, uploadedFile, next);
+			}
+		], next);
 	}, next);
 };
 
@@ -66,27 +75,32 @@ uploadsController.uploadThumb = function(req, res, next) {
 	}
 
 	uploadsController.upload(req, res, function(uploadedFile, next) {
-		file.isFileTypeAllowed(uploadedFile.path, function(err, tempPath) {
+		file.isFileTypeAllowed(uploadedFile.path, function(err) {
 			if (err) {
 				return next(err);
 			}
 
-			if (uploadedFile.type.match(/image./)) {
-				var size = parseInt(meta.config.topicThumbSize, 10) || 120;
-				image.resizeImage({
-					path: uploadedFile.path,
-					extension: path.extname(uploadedFile.name),
-					width: size,
-					height: size
-				}, function(err) {
-					if (err) {
-						return next(err);
-					}
-					uploadImage(req.user ? req.user.uid : 0, uploadedFile, next);
-				});
-			} else {
-				next(new Error('[[error:invalid-file]]'));
+			if (!uploadedFile.type.match(/image./)) {
+				return next(new Error('[[error:invalid-file]]'));
 			}
+
+			var size = parseInt(meta.config.topicThumbSize, 10) || 120;
+			image.resizeImage({
+				path: uploadedFile.path,
+				extension: path.extname(uploadedFile.name),
+				width: size,
+				height: size
+			}, function(err) {
+				if (err) {
+					return next(err);
+				}
+
+				if (plugins.hasListeners('filter:uploadImage')) {
+					return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, next);
+				}
+
+				uploadFile(req.uid, uploadedFile, next);
+			});
 		});
 	}, next);
 };
@@ -100,30 +114,19 @@ uploadsController.uploadGroupCover = function(uid, uploadedFile, callback) {
 		return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback);
 	}
 
-	saveFileToLocal(uploadedFile, callback);
+	file.isFileTypeAllowed(uploadedFile.path, function(err) {
+		if (err) {
+			return callback(err);
+		}
+		saveFileToLocal(uploadedFile, callback);
+	});
 };
 
-function uploadImage(uid, image, callback) {
-	if (plugins.hasListeners('filter:uploadImage')) {
-		return plugins.fireHook('filter:uploadImage', {image: image, uid: uid}, callback);
-	}
-
-	if (parseInt(meta.config.allowFileUploads, 10)) {
-		uploadFile(uid, image, callback);
-	} else {
-		callback(new Error('[[error:uploads-are-disabled]]'));
-	}
-}
-
 function uploadFile(uid, uploadedFile, callback) {
 	if (plugins.hasListeners('filter:uploadFile')) {
 		return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback);
 	}
 
-	if (parseInt(meta.config.allowFileUploads, 10) !== 1) {
-		return callback(new Error('[[error:uploads-are-disabled]]'));
-	}
-
 	if (!uploadedFile) {
 		return callback(new Error('[[error:invalid-file]]'));
 	}
diff --git a/src/controllers/users.js b/src/controllers/users.js
index b014ac3452..31ac0bd1cc 100644
--- a/src/controllers/users.js
+++ b/src/controllers/users.js
@@ -13,10 +13,18 @@ var helpers = require('./helpers');
 var usersController = {};
 
 usersController.getOnlineUsers = function(req, res, next) {
-	usersController.getUsers('users:online', req.uid, req.query.page, function(err, userData) {
+	async.parallel({
+		users: function(next) {
+			usersController.getUsers('users:online', req.uid, req.query.page, next);
+		},
+		guests: function(next) {
+			require('../socket.io/admin/rooms').getTotalGuestCount(next);
+		}
+	}, function(err, results) {
 		if (err) {
 			return next(err);
 		}
+		var userData = results.users;
 		var hiddenCount = 0;
 		if (!userData.isAdminOrGlobalMod) {
 			userData.users = userData.users.filter(function(user) {
@@ -27,7 +35,7 @@ usersController.getOnlineUsers = function(req, res, next) {
 			});
 		}
 
-		userData.anonymousUserCount = require('../socket.io').getOnlineAnonCount() + hiddenCount;
+		userData.anonymousUserCount = results.guests + hiddenCount;
 
 		render(req, res, userData, next);
 	});
@@ -95,7 +103,7 @@ usersController.getUsers = function(set, uid, page, callback) {
 	}
 
 	page = parseInt(page, 10) || 1;
-	var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20;
+	var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 50;
 	var start = Math.max(0, page - 1) * resultsPerPage;
 	var stop = start + resultsPerPage - 1;
 
@@ -137,7 +145,7 @@ usersController.getUsersAndCount = function(set, uid, start, stop, callback) {
 		count: function(next) {
 			if (set === 'users:online') {
 				var now = Date.now();
-				db.sortedSetCount('users:online', now - 300000, now, next);
+				db.sortedSetCount('users:online', now - 300000, '+inf', next);
 			} else if (set === 'users:banned') {
 				db.sortedSetCard('users:banned', next);
 			} else {
diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js
index fe80afaaf4..dbf294119e 100644
--- a/src/database/mongo/hash.js
+++ b/src/database/mongo/hash.js
@@ -1,7 +1,5 @@
 "use strict";
 
-var winston = require('winston');
-
 module.exports = function(db, module) {
 	var helpers = module.helpers.mongo;
 
@@ -121,9 +119,8 @@ module.exports = function(db, module) {
 			}
 
 			var map = helpers.toMap(items);
-			var returnData = [],
-				index = 0,
-				item;
+			var returnData = [];
+			var item;
 
 			for (var i=0; i<keys.length; ++i) {
 				item = map[keys[i]] || {};
@@ -219,7 +216,7 @@ module.exports = function(db, module) {
 			data[field] = '';
 		});
 
-		db.collection('objects').update({_key: key}, {$unset : data}, function(err, res) {
+		db.collection('objects').update({_key: key}, {$unset : data}, function(err) {
 			callback(err);
 		});
 	};
diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js
index ba392fa42a..d8a9205ce0 100644
--- a/src/database/mongo/sorted.js
+++ b/src/database/mongo/sorted.js
@@ -37,7 +37,7 @@ module.exports = function(db, module) {
 			bulk.find({_key: key, value: values[i]}).upsert().updateOne({$set: {score: parseInt(scores[i], 10)}});
 		}
 
-		bulk.execute(function(err, result) {
+		bulk.execute(function(err) {
 			callback(err);
 		});
 	}
@@ -55,7 +55,7 @@ module.exports = function(db, module) {
 			bulk.find({_key: keys[i], value: value}).upsert().updateOne({$set: {score: parseInt(score, 10)}});
 		}
 
-		bulk.execute(function(err, result) {
+		bulk.execute(function(err) {
 			callback(err);
 		});
 	};
@@ -85,7 +85,7 @@ module.exports = function(db, module) {
 		}
 		value = helpers.valueToString(value);
 
-		db.collection('objects').remove({_key: {$in: keys}, value: value}, function(err, res) {
+		db.collection('objects').remove({_key: {$in: keys}, value: value}, function(err) {
 			callback(err);
 		});
 	};
@@ -95,7 +95,17 @@ module.exports = function(db, module) {
 		if (!Array.isArray(keys) || !keys.length) {
 			return callback();
 		}
-		db.collection('objects').remove({_key: {$in: keys}, score: {$lte: max, $gte: min}}, function(err) {
+		var query = {_key: {$in: keys}};
+
+		if (min !== '-inf') {
+			query.score = {$gte: min};
+		}
+		if (max !== '+inf') {
+			query.score = query.score || {};
+			query.score.$lte = max;
+		}
+
+		db.collection('objects').remove(query, function(err) {
 			callback(err);
 		});
 	};
@@ -125,6 +135,11 @@ module.exports = function(db, module) {
 		if (withScores) {
 			fields.score = 1;
 		}
+
+		if (Array.isArray(key)) {
+			key = {$in: key};
+		}
+
 		db.collection('objects').find({_key: key}, {fields: fields})
 			.limit(stop - start + 1)
 			.skip(start)
@@ -168,12 +183,14 @@ module.exports = function(db, module) {
 			count = 0;
 		}
 
-		var scoreQuery = {};
+		var query = {_key: key};
+
 		if (min !== '-inf') {
-			scoreQuery.$gte = min;
+			query.score = {$gte: min};
 		}
 		if (max !== '+inf') {
-			scoreQuery.$lte = max;
+			query.score = query.score || {};
+			query.score.$lte = max;
 		}
 
 		var fields = {_id: 0, value: 1};
@@ -181,7 +198,7 @@ module.exports = function(db, module) {
 			fields.score = 1;
 		}
 
-		db.collection('objects').find({_key:key, score: scoreQuery}, {fields: fields})
+		db.collection('objects').find(query, {fields: fields})
 			.limit(count)
 			.skip(start)
 			.sort({score: sort})
@@ -459,6 +476,7 @@ module.exports = function(db, module) {
 		getSortedSetUnion(sets, -1, start, stop, callback);
 	};
 
+
 	function getSortedSetUnion(sets, sort, start, stop, callback) {
 		if (!Array.isArray(sets) || !sets.length) {
 			return callback();
@@ -506,6 +524,13 @@ module.exports = function(db, module) {
 		data.score = parseInt(increment, 10);
 
 		db.collection('objects').findAndModify({_key: key, value: value}, {}, {$inc: data}, {new: true, upsert: true}, function(err, result) {
+			// if there is duplicate key error retry the upsert
+			// https://github.com/NodeBB/NodeBB/issues/4467
+			// https://jira.mongodb.org/browse/SERVER-14322
+			// https://docs.mongodb.org/manual/reference/command/findAndModify/#upsert-and-unique-index
+			if (err && err.message.startsWith('E11000 duplicate key error')) {
+				return module.sortedSetIncrBy(key, increment, value, callback);
+			}
 			callback(err, result && result.value ? result.value.score : null);
 		});
 	};
@@ -533,4 +558,41 @@ module.exports = function(db, module) {
 				callback(err, data);
 		});
 	};
-};
\ No newline at end of file
+
+	module.processSortedSet = function(setKey, process, batch, callback) {
+		var done = false;
+		var ids = [];
+		var cursor = db.collection('objects').find({_key: setKey})
+			.sort({score: 1})
+			.project({_id: 0, value: 1})
+			.batchSize(batch);
+
+		async.whilst(
+			function() {
+				return !done;
+			},
+			function(next) {
+				cursor.next(function(err, item) {
+					if (err) {
+						return next(err);
+					}
+					if (item === null) {
+						done = true;
+					} else {
+						ids.push(item.value);
+					}
+
+					if (ids.length < batch && (!done || ids.length === 0)) {
+						return next(null);
+					}
+
+					process(ids, function(err) {
+						ids = [];
+						return next(err);
+					});
+				});
+			},
+			callback
+		);
+	};
+};
diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js
index 8d9b35896d..0341c043f7 100644
--- a/src/database/redis/sorted.js
+++ b/src/database/redis/sorted.js
@@ -76,29 +76,41 @@ module.exports = function(redisClient, module) {
 	};
 
 	module.getSortedSetRange = function(key, start, stop, callback) {
-		redisClient.zrange(key, start, stop, callback);
+		sortedSetRange('zrange', key, start, stop, false, callback);
 	};
 
 	module.getSortedSetRevRange = function(key, start, stop, callback) {
-		redisClient.zrevrange(key, start, stop, callback);
+		sortedSetRange('zrevrange', key, start, stop, false, callback);
 	};
 
 	module.getSortedSetRangeWithScores = function(key, start, stop, callback) {
-		sortedSetRangeWithScores('zrange', key, start, stop, callback);
+		sortedSetRange('zrange', key, start, stop, true, callback);
 	};
 
 	module.getSortedSetRevRangeWithScores = function(key, start, stop, callback) {
-		sortedSetRangeWithScores('zrevrange', key, start, stop, callback);
+		sortedSetRange('zrevrange', key, start, stop, true, callback);
 	};
 
-	function sortedSetRangeWithScores(method, key, start, stop, callback) {
-		redisClient[method]([key, start, stop, 'WITHSCORES'], function(err, data) {
+	function sortedSetRange(method, key, start, stop, withScores, callback) {
+		if (Array.isArray(key)) {
+			return sortedSetUnion(method, key, start, stop, withScores, callback);
+		}
+
+		var params = [key, start, stop];
+		if (withScores) {
+			params.push('WITHSCORES');
+		}
+
+		redisClient[method](params, function(err, data) {
 			if (err) {
 				return callback(err);
 			}
+			if (!withScores) {
+				return callback(null, data);
+			}
 			var objects = [];
 			for(var i=0; i<data.length; i+=2) {
-				objects.push({value: data[i], score: data[i+1]});
+				objects.push({value: data[i], score: data[i + 1]});
 			}
 			callback(null, objects);
 		});
@@ -221,25 +233,39 @@ module.exports = function(redisClient, module) {
 	};
 
 	module.getSortedSetUnion = function(sets, start, stop, callback) {
-		sortedSetUnion(sets, false, start, stop, callback);
+		sortedSetUnion('zrange', sets, start, stop, false, callback);
 	};
 
 	module.getSortedSetRevUnion = function(sets, start, stop, callback) {
-		sortedSetUnion(sets, true, start, stop, callback);
+		sortedSetUnion('zrevrange', sets, start, stop, false, callback);
 	};
 
-	function sortedSetUnion(sets, reverse, start, stop, callback) {
-		var	multi = redisClient.multi();
+	function sortedSetUnion(method, sets, start, stop, withScores, callback) {
 
-		// zunionstore prep
-		sets.unshift(sets.length);
-		sets.unshift('temp');
+		var tempSetName = 'temp_' + Date.now();
+
+		var params = [tempSetName, start, stop];
+		if (withScores) {
+			params.push('WITHSCORES');
+		}
 
-		multi.zunionstore.apply(multi, sets);
-		multi[reverse ? 'zrevrange' : 'zrange']('temp', start, stop);
-		multi.del('temp');
+		var	multi = redisClient.multi();
+		multi.zunionstore([tempSetName, sets.length].concat(sets));
+		multi[method](params);
+		multi.del(tempSetName);
 		multi.exec(function(err, results) {
-			callback(err, results ? results[1] : null);
+			if (err) {
+				return callback(err);
+			}
+			if (!withScores) {
+				return callback(null, results ? results[1] : null);
+			}
+			results = results[1] || [];
+			var objects = [];
+			for(var i=0; i<results.length; i+=2) {
+				objects.push({value: results[i], score: results[i + 1]});
+			}
+			callback(null, objects);
 		});
 	}
 
diff --git a/src/file.js b/src/file.js
index 7fe5aa8f06..1c1cab7781 100644
--- a/src/file.js
+++ b/src/file.js
@@ -52,7 +52,7 @@ file.base64ToLocal = function(imageData, uploadPath, callback) {
 file.isFileTypeAllowed = function(path, callback) {
 	// Attempt to read the file, if it passes, file type is allowed
 	jimp.read(path, function(err) {
-		callback(err, path);
+		callback(err);
 	});
 };
 
diff --git a/src/groups.js b/src/groups.js
index db5974e54e..2df5dc3ce2 100644
--- a/src/groups.js
+++ b/src/groups.js
@@ -240,9 +240,9 @@ var utils = require('../public/src/utils');
 	Groups.escapeGroupData = function(group) {
 		if (group) {
 			group.nameEncoded = encodeURIComponent(group.name);
-			group.displayName = validator.escape(group.name);
-			group.description = validator.escape(group.description || '');
-			group.userTitle = validator.escape(group.userTitle || '') || group.displayName;
+			group.displayName = validator.escape(String(group.name));
+			group.description = validator.escape(String(group.description || ''));
+			group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName;
 		}
 	};
 
@@ -357,7 +357,7 @@ var utils = require('../public/src/utils');
 				var keys = uids.map(function(uid) {
 					return 'uid:' + uid + ':posts';
 				});
-				db.getSortedSetRevUnion(keys, 0, max - 1, next);
+				db.getSortedSetRevRange(keys, 0, max - 1, next);
 			},
 			function(pids, next) {
 				privileges.posts.filter('read', pids, uid, next);
@@ -425,9 +425,13 @@ var utils = require('../public/src/utils');
 	};
 
 	Groups.getUserGroups = function(uids, callback) {
+		Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback);
+	};
+
+	Groups.getUserGroupsFromSet = function (set, uids, callback) {
 		async.waterfall([
 			function(next) {
-				db.getSortedSetRevRange('groups:visible:createtime', 0, -1, next);
+				db.getSortedSetRevRange(set, 0, -1, next);
 			},
 			function(groupNames, next) {
 				var groupSets = groupNames.map(function(name) {
diff --git a/src/groups/cover.js b/src/groups/cover.js
index 492138e75a..89ef97df94 100644
--- a/src/groups/cover.js
+++ b/src/groups/cover.js
@@ -7,7 +7,6 @@ var fs = require('fs');
 var crypto = require('crypto');
 var Jimp = require('jimp');
 
-
 var db = require('../database');
 var file = require('../file');
 var uploadsController = require('../controllers/uploads');
@@ -39,11 +38,7 @@ module.exports = function(Groups) {
 				writeImageDataToFile(data.imageData, next);
 			},
 			function (_tempPath, next) {
-				tempPath = _tempPath;	// set in local var so it can be deleted if file type invalid
-				next(null, tempPath);
-			},
-			async.apply(file.isFileTypeAllowed),
-			function (_tempPath, next) {
+				tempPath = _tempPath;
 				uploadsController.uploadGroupCover(uid, {
 					name: 'groupCover',
 					path: tempPath
diff --git a/src/groups/membership.js b/src/groups/membership.js
index cb37121a40..747fa9d3d7 100644
--- a/src/groups/membership.js
+++ b/src/groups/membership.js
@@ -93,7 +93,8 @@ module.exports = function(Groups) {
 							bodyShort: '[[groups:request.notification_title, ' + username + ']]',
 							bodyLong: '[[groups:request.notification_text, ' + username + ', ' + groupName + ']]',
 							nid: 'group:' + groupName + ':uid:' + uid + ':request',
-							path: '/groups/' + utils.slugify(groupName)
+							path: '/groups/' + utils.slugify(groupName),
+							from: uid
 						}, next);
 					},
 					owners: function(next) {
@@ -413,4 +414,23 @@ module.exports = function(Groups) {
 		}
 		db.getSetMembers('group:' + groupName + ':pending', callback);
 	};
+	
+	Groups.kick = function(uid, groupName, isOwner, callback) {
+		if (isOwner) {
+			// If the owners set only contains one member, error out!
+			async.waterfall([
+				function (next) {
+					db.setCount('group:' + groupName + ':owners', next);
+				},
+				function (numOwners, next) {
+					if (numOwners <= 1) {
+						return next(new Error('[[error:group-needs-owner]]'));
+					}
+					Groups.leave(groupName, uid, next);
+				}
+			], callback);
+		} else {
+			Groups.leave(groupName, uid, callback);
+		}
+	};
 };
diff --git a/src/image.js b/src/image.js
index 2c5c5ceb8c..f4b85acd4d 100644
--- a/src/image.js
+++ b/src/image.js
@@ -72,7 +72,9 @@ image.normalise = function(path, extension, callback) {
 			if (err) {
 				return callback(err);
 			}
-			image.write(path + '.png', callback);
+			image.write(path + '.png', function(err) {
+				callback(err);
+			});
 		});
 	}
 };
diff --git a/src/install.js b/src/install.js
index 129c50c548..846f6f9050 100644
--- a/src/install.js
+++ b/src/install.js
@@ -339,7 +339,7 @@ function createGlobalModeratorsGroup(next) {
 		function (exists, next) {
 			if (exists) {
 				winston.info('Global Moderators group found, skipping creation!');
-				return next();
+				return next(null, null);
 			}
 			groups.create({
 				name: 'Global Moderators',
diff --git a/src/messaging.js b/src/messaging.js
index e39e9f0d3b..1974313c81 100644
--- a/src/messaging.js
+++ b/src/messaging.js
@@ -314,7 +314,7 @@ var async = require('async'),
 
 	Messaging.canMessageUser = function(uid, toUid, callback) {
 		if (parseInt(meta.config.disableChat) === 1 || !uid || uid === toUid) {
-			return callback(null, false);
+			return callback(new Error('[[error:chat-disabled]]'));
 		}
 
 		async.waterfall([
@@ -323,43 +323,40 @@ var async = require('async'),
 			},
 			function (exists, next) {
 				if (!exists) {
-					return callback(null, false);
+					return callback(new Error('[[error:no-user]]'));
 				}
 				user.getUserFields(uid, ['banned', 'email:confirmed'], next);
 			},
 			function (userData, next) {
 				if (parseInt(userData.banned, 10) === 1) {
-					return callback(null, false);
+					return callback(new Error('[[error:user-banned]]'));
 				}
 
 				if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) {
-					return callback(null, false);
+					return callback(new Error('[[error:email-not-confirmed-chat]]'));
 				}
 
-				user.getSettings(toUid, next);
+				async.parallel({
+					settings: async.apply(user.getSettings, toUid),
+					isAdmin: async.apply(user.isAdministrator, uid),
+					isFollowing: async.apply(user.isFollowing, toUid, uid)
+				}, next);
 			},
-			function(settings, next) {
-				if (!settings.restrictChat) {
-					return callback(null, true);
+			function(results, next) {
+				if (!results.settings.restrictChat || results.isAdmin || results.isFollowing) {
+					return next();
 				}
 
-				user.isAdministrator(uid, next);
-			},
-			function(isAdmin, next) {
-				if (isAdmin) {
-					return callback(null, true);
-				}
-				user.isFollowing(toUid, uid, next);
+ 				next(new Error('[[error:chat-restricted]]'));
 			}
 		], callback);
-
 	};
 
 	Messaging.canMessageRoom = function(uid, roomId, callback) {
 		if (parseInt(meta.config.disableChat) === 1 || !uid) {
 			return callback(new Error('[[error:chat-disabled]]'));
 		}
-		
+
 		async.waterfall([
 			function (next) {
 				Messaging.isUserInRoom(uid, roomId, next);
@@ -368,14 +365,14 @@ var async = require('async'),
 				if (!inRoom) {
 					return next(new Error('[[error:not-in-room]]'));
 				}
-				
+
 				Messaging.getUserCountInRoom(roomId, next);
 			},
 			function(count, next) {
 				if (count < 2) {
 					return next(new Error('[[error:no-users-in-room]]'));
 				}
-				
+
 				user.getUserFields(uid, ['banned', 'email:confirmed'], next);
 			},
 			function (userData, next) {
diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js
index 784c6cb2c0..0c9c5bb01f 100644
--- a/src/messaging/rooms.js
+++ b/src/messaging/rooms.js
@@ -147,7 +147,10 @@ module.exports = function(Messaging) {
 		if (!newName) {
 			return callback(new Error('[[error:invalid-name]]'));
 		}
-
+		newName = newName.trim();
+		if (newName.length > 75) {
+			return callback(new Error('[[error:chat-room-name-too-long]]'));
+		}
 		async.waterfall([
 			function (next) {
 				Messaging.isRoomOwner(uid, roomId, next);
diff --git a/src/messaging/unread.js b/src/messaging/unread.js
index 4bceaab3ce..0562551540 100644
--- a/src/messaging/unread.js
+++ b/src/messaging/unread.js
@@ -8,10 +8,16 @@ var sockets = require('../socket.io');
 module.exports = function(Messaging) {
 
 	Messaging.getUnreadCount = function(uid, callback) {
+		if (!parseInt(uid, 10)) {
+			return callback(null, 0);
+		}
 		db.sortedSetCard('uid:' + uid + ':chat:rooms:unread', callback);
 	};
 
 	Messaging.pushUnreadCount = function(uid) {
+		if (!parseInt(uid, 10)) {
+			return callback(null, 0);
+		}
 		Messaging.getUnreadCount(uid, function(err, unreadCount) {
 			if (err) {
 				return;
@@ -24,6 +30,10 @@ module.exports = function(Messaging) {
 		db.sortedSetRemove('uid:' + uid + ':chat:rooms:unread', roomId, callback);
 	};
 
+	Messaging.markAllRead = function(uid, callback) {
+		db.delete('uid:' + uid + ':chat:rooms:unread', callback);
+	};
+
 	Messaging.markUnread = function(uids, roomId, callback) {
 		async.waterfall([
 			function (next) {
diff --git a/src/meta.js b/src/meta.js
index 8fec4c3444..78251d4996 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -26,6 +26,7 @@ var async = require('async'),
 	require('./meta/tags')(Meta);
 	require('./meta/dependencies')(Meta);
 	Meta.templates = require('./meta/templates');
+	Meta.blacklist = require('./meta/blacklist');
 
 	/* Assorted */
 	Meta.userOrGroupExists = function(slug, callback) {
@@ -60,21 +61,17 @@ var async = require('async'),
 			async.apply(plugins.clearRequireCache),
 			async.apply(plugins.reload),
 			async.apply(plugins.reloadRoutes),
+			async.apply(Meta.js.symlinkModules),
+			async.apply(Meta.css.minify),
+			async.apply(Meta.js.minify, 'nodebb.min.js'),
+			async.apply(Meta.js.minify, 'acp.min.js'),
+			async.apply(Meta.sounds.init),
+			async.apply(Meta.templates.compile),
+			async.apply(auth.reloadRoutes),
 			function(next) {
-				async.parallel([
-					async.apply(Meta.js.symlinkModules),
-					async.apply(Meta.js.minify, 'nodebb.min.js'),
-					async.apply(Meta.js.minify, 'acp.min.js'),
-					async.apply(Meta.css.minify),
-					async.apply(Meta.sounds.init),
-					async.apply(Meta.templates.compile),
-					async.apply(auth.reloadRoutes),
-					function(next) {
-						Meta.config['cache-buster'] = utils.generateUUID();
-						templates.flush();
-						next();
-					}
-				], next);
+				Meta.config['cache-buster'] = utils.generateUUID();
+				templates.flush();
+				next();
 			}
 		], function(err) {
 			if (!err) {
diff --git a/src/meta/blacklist.js b/src/meta/blacklist.js
new file mode 100644
index 0000000000..47381e487b
--- /dev/null
+++ b/src/meta/blacklist.js
@@ -0,0 +1,126 @@
+'use strict';
+
+var ip = require('ip');
+var winston = require('winston');
+var async = require('async');
+var db = require('../database');
+
+var Blacklist = {
+		_rules: []
+	};
+
+Blacklist.load = function(callback) {
+	async.waterfall([
+		async.apply(db.get, 'ip-blacklist-rules'),
+		async.apply(Blacklist.validate)
+	], function(err, rules) {
+		if (err) {
+			return callback(err);
+		}
+
+		winston.verbose('[meta/blacklist] Loading ' + rules.valid.length + ' blacklist rules');
+		if (rules.invalid.length) {
+			winston.warn('[meta/blacklist] ' + rules.invalid.length + ' invalid blacklist rule(s) were ignored.');
+		}
+
+		Blacklist._rules = {
+			ipv4: rules.ipv4,
+			ipv6: rules.ipv6,
+			cidr: rules.cidr
+		};
+
+		callback();
+	});
+};
+
+Blacklist.save = function(rules, callback) {
+	db.set('ip-blacklist-rules', rules, function(err) {
+		if (err) {
+			return callback(err);
+		}
+		Blacklist.load(callback);
+	});
+};
+
+Blacklist.get = function(callback) {
+	db.get('ip-blacklist-rules', callback);
+};
+
+Blacklist.test = function(clientIp, callback) {
+	if (
+		Blacklist._rules.ipv4.indexOf(clientIp) === -1	// not explicitly specified in ipv4 list
+		&& Blacklist._rules.ipv6.indexOf(clientIp) === -1	// not explicitly specified in ipv6 list
+		&& !Blacklist._rules.cidr.some(function(subnet) {
+			return ip.cidrSubnet(subnet).contains(clientIp);
+		})	// not in a blacklisted cidr range
+	) {
+		if (typeof callback === 'function') {
+			callback();
+		} else {
+			return false;
+		}
+	} else {
+		var err = new Error('[[error:blacklisted-ip]]');
+		err.code = 'blacklisted-ip';
+
+		if (typeof callback === 'function') {
+			callback(err);
+		} else {
+			return true;
+		}
+	}
+};
+
+Blacklist.validate = function(rules, callback) {
+	rules = (rules || '').split('\n');
+	var ipv4 = [];
+	var ipv6 = [];
+	var cidr = [];
+	var invalid = [];
+
+	var isCidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/,
+		inlineCommentMatch = /#.*$/,
+		whitelist = ['127.0.0.1', '::1', '::ffff:0:127.0.0.1'];
+
+	// Filter out blank lines and lines starting with the hash character (comments)
+	// Also trim inputs and remove inline comments
+	rules = rules.map(function(rule) {
+		rule = rule.replace(inlineCommentMatch, '').trim();
+		return rule.length && !rule.startsWith('#') ? rule : null;
+	}).filter(Boolean);
+
+	// Filter out invalid rules
+	rules = rules.filter(function(rule) {
+		if (whitelist.indexOf(rule) !== -1) {
+			invalid.push(rule);
+			return false;
+		}
+
+		if (ip.isV4Format(rule)) {
+			ipv4.push(rule);
+			return true;
+		} else if (ip.isV6Format(rule)) {
+			ipv6.push(rule);
+			return true;
+		} else if (isCidrSubnet.test(rule)) {
+			cidr.push(rule);
+			return true;
+		} else {
+			invalid.push(rule);
+			return false;
+		}
+
+		return true;
+	});
+
+	callback(null, {
+		numRules: rules.length + invalid.length,
+		ipv4: ipv4,
+		ipv6: ipv6,
+		cidr: cidr,
+		valid: rules,
+		invalid: invalid
+	});
+};
+
+module.exports = Blacklist;
\ No newline at end of file
diff --git a/src/meta/css.js b/src/meta/css.js
index effd560526..12c5d49c20 100644
--- a/src/meta/css.js
+++ b/src/meta/css.js
@@ -43,7 +43,8 @@ module.exports = function(Meta) {
 					path.join(__dirname, '../../public/vendor/fontawesome/less'),
 					path.join(__dirname, '../../public/vendor/bootstrap/less')
 				],
-				source = '@import "font-awesome";';
+				source = '@import "font-awesome";',
+				acpSource = '@import "font-awesome";';
 
 			plugins.lessFiles = filterMissingFiles(plugins.lessFiles);
 			plugins.cssFiles = filterMissingFiles(plugins.cssFiles);
@@ -67,23 +68,35 @@ module.exports = function(Meta) {
 
 				source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/jquery/css/smoothness/jquery-ui-1.10.4.custom.min.css";';
 				source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css";';
-				source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/jquery/textcomplete/jquery.textcomplete.css";';
-				source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/colorpicker/colorpicker.css";';
 				source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/flags.less";';
+				source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/blacklist.less";';
 				source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/generics.less";';
 				source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/mixins.less";';
+				source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/global.less";';
+				source = '@import "./theme";\n' + source;
 
-				var acpSource = '\n@import "..' + path.sep + 'public/less/admin/admin";\n' + source;
+				acpSource += '\n@import "..' + path.sep + 'public/less/admin/admin";\n';
 				acpSource += '\n@import "..' + path.sep + 'public/less/generics.less";';
 				acpSource += '\n@import (inline) "..' + path.sep + 'public/vendor/colorpicker/colorpicker.css";';
 
-				source = '@import "./theme";\n' + source;
 
-				async.parallel([
+				var fromFile = nconf.get('from-file') || '';
+				
+				async.series([
 					function(next) {
+						if (fromFile.match('clientLess')) {
+							winston.info('[minifier] Compiling front-end LESS files skipped');
+							return Meta.css.getFromFile(path.join(__dirname, '../../public/stylesheet.css'), 'cache', next);
+						}
+
 						minify(source, paths, 'cache', next);
 					},
 					function(next) {
+						if (fromFile.match('acpLess')) {
+							winston.info('[minifier] Compiling ACP LESS files skipped');
+							return Meta.css.getFromFile(path.join(__dirname, '../../public/admin.css'), 'acpCache', next);
+						}
+						
 						minify(acpSource, paths, 'acpCache', next);
 					}
 				], function(err, minified) {
@@ -95,8 +108,8 @@ module.exports = function(Meta) {
 					if (process.send) {
 						process.send({
 							action: 'css-propagate',
-							cache: minified[0],
-							acpCache: minified[1]
+							cache: fromFile.match('clientLess') ? Meta.css.cache : minified[0],
+							acpCache: fromFile.match('acpLess') ? Meta.css.acpCache : minified[1]
 						});
 					}
 
@@ -137,7 +150,7 @@ module.exports = function(Meta) {
 		});
 	}
 
-	Meta.css.commitToFile = function(filename) {
+	Meta.css.commitToFile = function(filename, callback) {
 		var file = (filename === 'acpCache' ? 'admin' : 'stylesheet') + '.css';
 
 		fs.writeFile(path.join(__dirname, '../../public/' + file), Meta.css[filename], function(err) {
@@ -147,31 +160,17 @@ module.exports = function(Meta) {
 				winston.error('[meta/css] ' + err.message);
 				process.exit(0);
 			}
+
+			callback();
 		});
 	};
 
-	Meta.css.getFromFile = function(callback) {
-		var cachePath = path.join(__dirname, '../../public/stylesheet.css'),
-			acpCachePath = path.join(__dirname, '../../public/admin.css');
-		file.exists(cachePath, function(exists) {
-			if (!exists) {
-				winston.warn('[meta/css] No stylesheets found on disk, re-minifying');
-				Meta.css.minify(callback);
-				return;
-			}
-
-			if (nconf.get('isPrimary') !== 'true') {
-				return callback();
-			}
+	Meta.css.getFromFile = function(filePath, filename, callback) {
+		winston.verbose('[meta/css] Reading stylesheet ' + filePath.split('/').pop() + ' from file');
 
-			winston.verbose('[meta/css] Reading stylesheets from file');
-			async.map([cachePath, acpCachePath], fs.readFile, function(err, files) {
-				Meta.css.cache = files[0];
-				Meta.css.acpCache = files[1];
-
-				emitter.emit('meta:css.compiled');
-				callback();
-			});
+		fs.readFile(filePath, function(err, file) {
+			Meta.css[filename] = file;
+			callback();
 		});
 	};
 
@@ -188,7 +187,6 @@ module.exports = function(Meta) {
 				return;
 			}
 
-			winston.verbose('[meta/css] Running PostCSS Plugins');
 			postcss([ autoprefixer ]).process(lessOutput.css).then(function (result) {
 				result.warnings().forEach(function (warn) {
 					winston.verbose(warn.toString());
@@ -198,7 +196,11 @@ module.exports = function(Meta) {
 
 				// Save the compiled CSS in public/ so things like nginx can serve it
 				if (nconf.get('isPrimary') === 'true') {
-					Meta.css.commitToFile(destination);
+					return Meta.css.commitToFile(destination, function() {
+						if (typeof callback === 'function') {
+							callback(null, result.css);
+						}
+					});
 				}
 
 				if (typeof callback === 'function') {
diff --git a/src/meta/js.js b/src/meta/js.js
index 9476141ed1..eaa7824705 100644
--- a/src/meta/js.js
+++ b/src/meta/js.js
@@ -4,7 +4,6 @@ var winston = require('winston'),
 	fork = require('child_process').fork,
 	path = require('path'),
 	async = require('async'),
-	_ = require('underscore'),
 	nconf = require('nconf'),
 	fs = require('fs'),
 	rimraf = require('rimraf'),
@@ -26,7 +25,7 @@ module.exports = function(Meta) {
 				'public/vendor/visibility/visibility.min.js',
 				'public/vendor/bootstrap/js/bootstrap.min.js',
 				'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js',
-				'public/vendor/jquery/textcomplete/jquery.textcomplete.min.js',
+				'public/vendor/jquery/textcomplete/jquery.textcomplete.js',
 				'public/vendor/requirejs/require.js',
 				'public/vendor/bootbox/bootbox.min.js',
 				'public/vendor/tinycon/tinycon.js',
@@ -135,6 +134,8 @@ module.exports = function(Meta) {
 			return;
 		}
 
+		winston.verbose('[meta/js] Minifying ' + target);
+
 		var forkProcessParams = setupDebugging();
 		var minifier = Meta.js.minifierProc = fork('minifier.js', [], forkProcessParams);
 
@@ -156,19 +157,19 @@ module.exports = function(Meta) {
 				winston.verbose('[meta/js] ' + target + ' minification complete');
 				minifier.kill();
 
-				if (process.send) {
+				if (process.send && Meta.js.target['nodebb.min.js'] && Meta.js.target['acp.min.js']) {
 					process.send({
 						action: 'js-propagate',
-						cache: Meta.js.target[target].cache,
-						map: Meta.js.target[target].map
+						data: Meta.js.target
 					});
 				}
 
-				Meta.js.commitToFile(target);
+				Meta.js.commitToFile(target, function() {					
+					if (typeof callback === 'function') {
+						callback();
+					}
+				});
 
-				if (typeof callback === 'function') {
-					callback();
-				}
 				break;
 			case 'error':
 				winston.error('[meta/js] Could not compile ' + target + ': ' + message.message);
@@ -230,15 +231,15 @@ module.exports = function(Meta) {
 		}
 	};
 
-	Meta.js.commitToFile = function(target) {
+	Meta.js.commitToFile = function(target, callback) {
 		fs.writeFile(path.join(__dirname, '../../public/' + target), Meta.js.target[target].cache, function (err) {
 			if (err) {
 				winston.error('[meta/js] ' + err.message);
 				process.exit(0);
 			}
 
-			winston.verbose('[meta/js] ' + target + ' committed to disk.');
 			emitter.emit('meta:js.compiled');
+			callback();
 		});
 	};
 
diff --git a/src/meta/tags.js b/src/meta/tags.js
index 8495b827a6..3747db61da 100644
--- a/src/meta/tags.js
+++ b/src/meta/tags.js
@@ -46,7 +46,7 @@ module.exports = function(Meta) {
 				var defaultLinks = [{
 					rel: "icon",
 					type: "image/x-icon",
-					href: nconf.get('relative_path') + '/favicon.ico'
+					href: nconf.get('relative_path') + '/favicon.ico?' + Meta.config['cache-buster']
 				}, {
 					rel: "manifest",
 					href: nconf.get('relative_path') + '/manifest.json'
diff --git a/src/meta/templates.js b/src/meta/templates.js
index 04d9d31009..88578b235e 100644
--- a/src/meta/templates.js
+++ b/src/meta/templates.js
@@ -21,10 +21,10 @@ Templates.compile = function(callback) {
 
 	if (nconf.get('isPrimary') === 'false' || fromFile.match('tpl')) {
 		if (fromFile.match('tpl')) {
+			emitter.emit('templates:compiled');
 			winston.info('[minifier] Compiling templates skipped');
 		}
 
-		emitter.emit('templates:compiled');
 		return callback();
 	}
 
@@ -48,20 +48,28 @@ function getBaseTemplates(theme) {
 }
 
 function preparePaths(baseTemplatesPaths, callback) {
-	var coreTemplatesPath = nconf.get('core_templates_path'),
-		viewsPath = nconf.get('views_dir');
+	var coreTemplatesPath = nconf.get('core_templates_path');
+	var viewsPath = nconf.get('views_dir');
 
 	async.waterfall([
-		async.apply(plugins.fireHook, 'static:templates.precompile', {}),
-		async.apply(plugins.getTemplates)
+		function (next) {
+			rimraf(viewsPath, next);
+		},
+		function (next) {
+			mkdirp(viewsPath, next);
+		},
+		function(viewsPath, next) {
+			plugins.fireHook('static:templates.precompile', {}, next);
+		},
+		function(next) {
+			plugins.getTemplates(next);
+		}
 	], function(err, pluginTemplates) {
 		if (err) {
 			return callback(err);
 		}
 
 		winston.verbose('[meta/templates] Compiling templates');
-		rimraf.sync(viewsPath);
-		mkdirp.sync(viewsPath);
 
 		async.parallel({
 			coreTpls: function(next) {
@@ -111,7 +119,7 @@ function compile(callback) {
 	var themeConfig = require(nconf.get('theme_config')),
 		baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')],
 		viewsPath = nconf.get('views_dir');
-	
+
 
 	preparePaths(baseTemplatesPaths, function(err, paths) {
 		if (err) {
diff --git a/src/middleware/header.js b/src/middleware/header.js
index c2149c16d1..c1ed6d60c2 100644
--- a/src/middleware/header.js
+++ b/src/middleware/header.js
@@ -68,21 +68,14 @@ module.exports = function(app, middleware) {
 
 		async.parallel({
 			scripts: function(next) {
-				plugins.fireHook('filter:scripts.get', [], function(err, scripts) {
-					if (err) {
-						return next(err);
-					}
-					var arr = [];
-					scripts.forEach(function(script) {
-						arr.push({src: script});
-					});
-
-					next(null, arr);
-				});
+				plugins.fireHook('filter:scripts.get', [], next);
 			},
 			isAdmin: function(next) {
 				user.isAdministrator(req.uid, next);
 			},
+			isGlobalMod: function(next) {
+				user.isGlobalModerator(req.uid, next);
+			},
 			user: function(next) {
 				if (req.uid) {
 					user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'status', 'email:confirmed', 'banned'], next);
@@ -110,10 +103,11 @@ module.exports = function(app, middleware) {
 			}
 
 			results.user.isAdmin = results.isAdmin;
+			results.user.isGlobalMod = results.isGlobalMod;
 			results.user.uid = parseInt(results.user.uid, 10);
 			results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
 
-			if (res.locals.config.bootswatchSkin !== 'default') {
+			if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 && res.locals.config.bootswatchSkin !== 'default') {
 				templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css';
 			}
 
@@ -122,6 +116,7 @@ module.exports = function(app, middleware) {
 			templateValues.metaTags = results.tags.meta;
 			templateValues.linkTags = results.tags.link;
 			templateValues.isAdmin = results.user.isAdmin;
+			templateValues.isGlobalMod = results.user.isGlobalMod;
 			templateValues.user = results.user;
 			templateValues.userJSON = JSON.stringify(results.user);
 			templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1 && meta.config.customCSS;
@@ -136,7 +131,9 @@ module.exports = function(app, middleware) {
 			templateValues.template = {name: res.locals.template};
 			templateValues.template[res.locals.template] = true;
 
-			templateValues.scripts = results.scripts;
+			templateValues.scripts = results.scripts.map(function(script) {
+				return {src: script};
+			});
 
 			if (req.route && req.route.path === '/') {
 				modifyTitle(templateValues);
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index d5e1d93388..d09270c3c9 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -26,7 +26,7 @@ var app,
 		helpers: require('../controllers/helpers')
 	};
 
-toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 70);
+toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 100);
 toobusy.interval(parseInt(meta.config.eventLoopInterval, 10) || 500);
 
 middleware.authenticate = function(req, res, next) {
@@ -257,6 +257,12 @@ middleware.busyCheck = function(req, res, next) {
 	}
 };
 
+middleware.applyBlacklist = function(req, res, next) {
+	meta.blacklist.test(req.ip, function(err) {
+		next(err);
+	});
+};
+
 module.exports = function(webserver) {
 	app = webserver;
 	middleware.admin = require('./admin')(webserver);
diff --git a/src/middleware/render.js b/src/middleware/render.js
index 9ae7a50253..79911f71bd 100644
--- a/src/middleware/render.js
+++ b/src/middleware/render.js
@@ -13,7 +13,7 @@ module.exports = function(middleware) {
 				req = this.req,
 				defaultFn = function(err, str){
 					if (err) {
-						return req.next(err);
+						return next(err);
 					}
 
 					self.send(str);
@@ -72,6 +72,7 @@ module.exports = function(middleware) {
 						var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB';
 						language = req.query.lang || language;
 						translator.translate(str, language, function(translated) {
+							translated = translator.unescape(translated);
 							translated = translated + '<script id="ajaxify-data" type="application/json">' + ajaxifyData + '</script>';
 							fn(err, translated);
 						});
@@ -95,4 +96,4 @@ module.exports = function(middleware) {
 		return parts.join(' ');
 	}
 
-};
\ No newline at end of file
+};
diff --git a/src/notifications.js b/src/notifications.js
index 86feff02b4..94cb5313b8 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -5,6 +5,7 @@ var async = require('async'),
 	cron = require('cron').CronJob,
 	nconf = require('nconf'),
 	S = require('string'),
+	_ = require('underscore'),
 
 	db = require('./database'),
 	User = require('./user'),
@@ -59,7 +60,7 @@ var async = require('async'),
 						if (userData.username === '[[global:guest]]') {
 							notification.bodyShort = notification.bodyShort.replace(/([\s\S]*?),[\s\S]*?,([\s\S]*?)/, '$1, [[global:guest]], $2');
 						}
-						
+
 						next(null, notification);
 					});
 					return;
@@ -80,6 +81,36 @@ var async = require('async'),
 		});
 	};
 
+	Notifications.findRelated = function(mergeIds, set, callback) {
+		// A related notification is one in a zset that has the same mergeId
+		var _nids;
+
+		async.waterfall([
+			async.apply(db.getSortedSetRevRange, set, 0, -1),
+			function(nids, next) {
+				_nids = nids;
+
+				var keys = nids.map(function(nid) {
+					return 'notifications:' + nid;
+				});
+
+				db.getObjectsFields(keys, ['mergeId'], next);
+			},
+		], function(err, sets) {
+			if (err) {
+				return callback(err);
+			}
+
+			sets = sets.map(function(set) {
+				return set.mergeId;
+			});
+
+			callback(null, _nids.filter(function(nid, idx) {
+				return mergeIds.indexOf(sets[idx]) !== -1;
+			}));
+		});
+	};
+
 	Notifications.create = function(data, callback) {
 		if (!data.nid) {
 			return callback(new Error('no-notification-id'));
@@ -183,10 +214,10 @@ var async = require('async'),
 				db.sortedSetsRemove(readKeys, notification.nid, next);
 			},
 			function(next) {
-				db.sortedSetsRemoveRangeByScore(unreadKeys, 0, oneWeekAgo, next);
+				db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next);
 			},
 			function(next) {
-				db.sortedSetsRemoveRangeByScore(readKeys, 0, oneWeekAgo, next);
+				db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next);
 			}
 		], function(err) {
 			if (err) {
@@ -255,15 +286,39 @@ var async = require('async'),
 			return 'notifications:' + nid;
 		});
 
-		db.getObjectsFields(notificationKeys, ['nid', 'datetime'], function(err, notificationData) {
+		async.waterfall([
+			async.apply(db.getObjectsFields, notificationKeys, ['mergeId']),
+			function(mergeIds, next) {
+				// Isolate mergeIds and find related notifications
+				mergeIds = mergeIds.map(function(set) {
+					return set.mergeId;
+				}).reduce(function(memo, mergeId, idx, arr) {
+					if (mergeId && idx === arr.indexOf(mergeId)) {
+						memo.push(mergeId);
+					}
+					return memo;
+				}, []);
+
+				Notifications.findRelated(mergeIds, 'uid:' + uid + ':notifications:unread', next);
+			},
+			function(relatedNids, next) {
+				notificationKeys = _.union(nids, relatedNids).map(function(nid) {
+					return 'notifications:' + nid;
+				});
+
+				db.getObjectsFields(notificationKeys, ['nid', 'datetime'], next);
+			}
+		], function(err, notificationData) {
 			if (err) {
 				return callback(err);
 			}
 
+			// Filter out notifications that didn't exist
 			notificationData = notificationData.filter(function(notification) {
 				return notification && notification.nid;
 			});
 
+			// Extract nid
 			nids = notificationData.map(function(notification) {
 				return notification.nid;
 			});
@@ -303,7 +358,7 @@ var async = require('async'),
 
 		var	cutoffTime = Date.now() - week;
 
-		db.getSortedSetRangeByScore('notifications', 0, 500, 0, cutoffTime, function(err, nids) {
+		db.getSortedSetRangeByScore('notifications', 0, 500, '-inf', cutoffTime, function(err, nids) {
 			if (err) {
 				return winston.error(err.message);
 			}
@@ -340,7 +395,8 @@ var async = require('async'),
 				'notifications:upvoted_your_post_in',
 				'notifications:user_started_following_you',
 				'notifications:user_posted_to',
-				'notifications:user_flagged_post_in'
+				'notifications:user_flagged_post_in',
+				'new_register'
 			],
 			isolated, differentiators, differentiator, modifyIndex, set;
 
@@ -359,7 +415,7 @@ var async = require('async'),
 
 			// Each isolated mergeId may have multiple differentiators, so process each separately
 			differentiators = isolated.reduce(function(cur, next) {
-				differentiator = next.mergeId.split('|')[1];
+				differentiator = next.mergeId.split('|')[1] || 0;
 				if (cur.indexOf(differentiator) === -1) {
 					cur.push(differentiator);
 				}
@@ -368,9 +424,14 @@ var async = require('async'),
 			}, []);
 
 			differentiators.forEach(function(differentiator) {
-				set = isolated.filter(function(notifObj) {
-					return notifObj.mergeId === (mergeId + '|' + differentiator);
-				});
+				if (differentiator === 0 && differentiators.length === 1) {
+					set = isolated;
+				} else {
+					set = isolated.filter(function(notifObj) {
+						return notifObj.mergeId === (mergeId + '|' + differentiator);
+					});
+				}
+
 				modifyIndex = notifications.indexOf(set[0]);
 				if (modifyIndex === -1 || set.length === 1) {
 					return notifications;
@@ -383,18 +444,26 @@ var async = require('async'),
 					case 'notifications:user_posted_to':
 					case 'notifications:user_flagged_post_in':
 						var usernames = set.map(function(notifObj) {
-							return notifObj.user.username;
+							return notifObj && notifObj.user && notifObj.user.username;
 						}).filter(function(username, idx, array) {
 							return array.indexOf(username) === idx;
 						});
 						var numUsers = usernames.length;
 
+						var title = S(notifications[modifyIndex].topicTitle || '').decodeHTMLEntities().s;
+						var titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
+						titleEscaped = titleEscaped ? (', ' + titleEscaped) : '';
+
 						if (numUsers === 2) {
-							notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + ', ' + notifications[modifyIndex].topicTitle + ']]';
+							notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + titleEscaped + ']]';
 						} else if (numUsers > 2) {
-							notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers-1) + ', ' + notifications[modifyIndex].topicTitle + ']]';
+							notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers - 1) + titleEscaped + ']]';
 						}
 						break;
+
+					case 'new_register':
+						notifications[modifyIndex].bodyShort = '[[notifications:' + mergeId + '_multiple, ' + set.length + ']]';
+						break;
 				}
 
 				// Filter out duplicates
@@ -403,7 +472,7 @@ var async = require('async'),
 						return true;
 					}
 
-					return !(notifObj.mergeId === (mergeId + '|' + differentiator) && idx !== modifyIndex);
+					return !(notifObj.mergeId === (mergeId + (differentiator ? '|' + differentiator : '')) && idx !== modifyIndex);
 				});
 			});
 
diff --git a/src/plugins.js b/src/plugins.js
index cb015cf1d1..853c6f44f2 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -1,23 +1,23 @@
 'use strict';
 
-var fs = require('fs'),
-	path = require('path'),
-	async = require('async'),
-	winston = require('winston'),
-	semver = require('semver'),
-	express = require('express'),
-	nconf = require('nconf'),
-
-	db = require('./database'),
-	emitter = require('./emitter'),
-	meta = require('./meta'),
-	translator = require('../public/src/modules/translator'),
-	utils = require('../public/src/utils'),
-	hotswap = require('./hotswap'),
-	file = require('./file'),
-
-	controllers = require('./controllers'),
-	app, middleware;
+var fs = require('fs');
+var path = require('path');
+var async = require('async');
+var winston = require('winston');
+var semver = require('semver');
+var express = require('express');
+var nconf = require('nconf');
+
+var db = require('./database');
+var emitter = require('./emitter');
+var translator = require('../public/src/modules/translator');
+var utils = require('../public/src/utils');
+var hotswap = require('./hotswap');
+var file = require('./file');
+
+var controllers = require('./controllers');
+var app;
+var middleware;
 
 (function(Plugins) {
 	require('./plugins/install')(Plugins);
@@ -115,7 +115,7 @@ var fs = require('fs'),
 					process.stdout.write('\n');
 					winston.warn('[plugins/load] The following plugins may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing. In the event of an unresponsive NodeBB caused by this plugin, run `./nodebb reset -p PLUGINNAME` to disable it.');
 					for(var x=0,numPlugins=Plugins.versionWarning.length;x<numPlugins;x++) {
-						process.stdout.write('  * '.yellow + Plugins.versionWarning[x].reset + '\n');
+						process.stdout.write('  * '.yellow + Plugins.versionWarning[x] + '\n');
 					}
 					process.stdout.write('\n');
 				}
@@ -183,13 +183,17 @@ var fs = require('fs'),
 					utils.walk(templatesPath, function(err, pluginTemplates) {
 						if (pluginTemplates) {
 							pluginTemplates.forEach(function(pluginTemplate) {
-								tplName = "/" + pluginTemplate.replace(templatesPath, '').substring(1);
+								if (pluginTemplate.endsWith('.tpl')) {
+									tplName = "/" + pluginTemplate.replace(templatesPath, '').substring(1);
 
-								if (templates.hasOwnProperty(tplName)) {
-									winston.verbose('[plugins] ' + tplName + ' replaced by ' + plugin.id);
-								}
+									if (templates.hasOwnProperty(tplName)) {
+										winston.verbose('[plugins] ' + tplName + ' replaced by ' + plugin.id);
+									}
 
-								templates[tplName] = pluginTemplate;
+									templates[tplName] = pluginTemplate;
+								} else {
+									winston.warn('[plugins] Skipping ' + pluginTemplate + ' by plugin ' + plugin.id);
+								}
 							});
 						} else {
 							winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.');
diff --git a/src/posts.js b/src/posts.js
index 0f3cad00f9..a3d19e7a7f 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -56,8 +56,8 @@ var async = require('async'),
 						return next();
 					}
 
-					post.relativeTime = utils.toISOString(post.timestamp);
-					post.relativeEditTime = parseInt(post.edited, 10) !== 0 ? utils.toISOString(post.edited) : '';
+					post.timestampISO = utils.toISOString(post.timestamp);
+					post.editedISO = parseInt(post.edited, 10) !== 0 ? utils.toISOString(post.edited) : '';
 					Posts.parsePost(post, next);
 				}, next);
 			},
diff --git a/src/posts/edit.js b/src/posts/edit.js
index ea7686fb20..e8af9ae2d4 100644
--- a/src/posts/edit.js
+++ b/src/posts/edit.js
@@ -41,7 +41,7 @@ module.exports = function(Posts) {
 				postData.content = data.content;
 				postData.edited = now;
 				postData.editor = data.uid;
-				plugins.fireHook('filter:post.edit', {post: postData, uid: data.uid}, next);
+				plugins.fireHook('filter:post.edit', {req: data.req, post: postData, uid: data.uid}, next);
 			},
 			function (result, next) {
 				postData = result.post;
@@ -118,19 +118,17 @@ module.exports = function(Posts) {
 
 			if (title) {
 				topicData.title = title;
-				topicData.slug = tid + '/' + utils.slugify(title);
+				topicData.slug = tid + '/' + (utils.slugify(title) || 'topic');
 			}
 
-			if (data.topic_thumb) {
-				topicData.thumb = data.topic_thumb;
-			}
+			topicData.thumb = data.topic_thumb || '';
 
 			data.tags = data.tags || [];
 
 			async.waterfall([
-				async.apply(plugins.fireHook,'filter:topic.edit', topicData),
-				function(topicData, next) {
-					db.setObject('topic:' + tid, topicData, next);
+				async.apply(plugins.fireHook, 'filter:topic.edit', {req: data.req, topic: topicData}),
+				function(results, next) {
+					db.setObject('topic:' + tid, results.topic, next);
 				},
 				function(next) {
 					topics.updateTags(tid, data.tags, next);
diff --git a/src/posts/parse.js b/src/posts/parse.js
index efa5667ccc..1af374305d 100644
--- a/src/posts/parse.js
+++ b/src/posts/parse.js
@@ -3,6 +3,7 @@
 
 var cache = require('./cache');
 var plugins = require('../plugins');
+var translator = require('../../public/src/modules/translator');
 
 module.exports = function(Posts) {
 
@@ -24,6 +25,8 @@ module.exports = function(Posts) {
 				return callback(err);
 			}
 
+			data.postData.content = translator.escape(data.postData.content);
+
 			if (global.env === 'production' && data.postData.pid) {
 				cache.set(data.postData.pid, data.postData.content);
 			}
diff --git a/src/posts/summary.js b/src/posts/summary.js
index 0d9822b30c..3cb5586bc6 100644
--- a/src/posts/summary.js
+++ b/src/posts/summary.js
@@ -81,7 +81,7 @@ module.exports = function(Posts) {
 					post.user = results.users[post.uid];
 					post.topic = results.topics[post.tid];
 					post.category = results.categories[post.topic.cid];
-					post.relativeTime = utils.toISOString(post.timestamp);
+					post.timestampISO = utils.toISOString(post.timestamp);
 
 					if (!post.content || !options.parse) {
 						if (options.stripTags) {
diff --git a/src/posts/user.js b/src/posts/user.js
index 0d41397960..cd675cbfa3 100644
--- a/src/posts/user.js
+++ b/src/posts/user.js
@@ -1,6 +1,7 @@
 'use strict';
 
 var async = require('async'),
+	validator = require('validator'),
 
 	db = require('../database'),
 	user = require('../user'),
@@ -69,6 +70,8 @@ module.exports = function(Posts) {
 				userData.picture = userData.picture || '';
 				userData.status = user.getStatus(userData);
 				userData.groupTitle = results.groupTitles[i].groupTitle;
+				userData.signature = validator.escape(userData.signature || '');
+				userData.fullname = validator.escape(userData.fullname || '');
 			});
 
 			async.map(userData, function(userData, next) {
diff --git a/src/privileges/topics.js b/src/privileges/topics.js
index 6cc66483e1..83e635fde9 100644
--- a/src/privileges/topics.js
+++ b/src/privileges/topics.js
@@ -37,7 +37,7 @@ module.exports = function(privileges) {
 
 			var disabled = parseInt(results.disabled, 10) === 1;
 			var locked = parseInt(topic.locked, 10) === 1;
-			var	isAdminOrMod = results.isAdministrator || results.isModerator;
+			var isAdminOrMod = results.isAdministrator || results.isModerator;
 			var editable = isAdminOrMod;
 			var deletable = isAdminOrMod || results.isOwner;
 
diff --git a/src/reset.js b/src/reset.js
index eae981ba5f..f1e27738e8 100644
--- a/src/reset.js
+++ b/src/reset.js
@@ -16,7 +16,11 @@ Reset.reset = function() {
 		}
 
 		if (nconf.get('t')) {
-			resetThemes();
+			if(nconf.get('t') === true) {
+				resetThemes();
+			} else {
+				resetTheme(nconf.get('t'));
+			}
 		} else if (nconf.get('p')) {
 			if (nconf.get('p') === true) {
 				resetPlugins();
@@ -46,8 +50,8 @@ Reset.reset = function() {
 			process.stdout.write('    -s\tsettings\n');
 			process.stdout.write('    -a\tall of the above\n');
 
-			process.stdout.write('\nPlugin reset flag (-p) can take a single argument\n');
-			process.stdout.write('    e.g. ./nodebb reset -p nodebb-plugin-mentions\n');
+			process.stdout.write('\nPlugin and theme reset flags (-p & -t) can take a single argument\n');
+			process.stdout.write('    e.g. ./nodebb reset -p nodebb-plugin-mentions, ./nodebb reset -t nodebb-theme-persona\n');
 			process.exit();
 		}
 	});
@@ -65,6 +69,26 @@ function resetSettings(callback) {
 	});
 }
 
+function resetTheme(themeId) {
+	var meta = require('./meta');
+	var fs = require('fs');
+	
+	fs.access('node_modules/' + themeId + '/package.json', function(err, fd) {
+		if (err) {
+			winston.warn('[reset] Theme `%s` is not installed on this forum', themeId);
+			process.exit();
+		} else {
+			meta.themes.set({
+				type: 'local',
+				id: themeId
+			}, function(err) {
+				winston.info('[reset] Theme reset to ' + themeId);
+				process.exit();
+			});		
+		}
+	});
+}
+
 function resetThemes(callback) {
 	var meta = require('./meta');
 
diff --git a/src/rewards/index.js b/src/rewards/index.js
index c693b550fc..1b46b190d9 100644
--- a/src/rewards/index.js
+++ b/src/rewards/index.js
@@ -68,7 +68,7 @@ function getIDsByCondition(condition, callback) {
 }
 
 function filterCompletedRewards(uid, rewards, callback) {
-	db.getSortedSetRangeByScoreWithScores('uid:' + uid + ':rewards', 0, -1, 1, Infinity, function(err, data) {
+	db.getSortedSetRangeByScoreWithScores('uid:' + uid + ':rewards', 0, -1, 1, '+inf', function(err, data) {
 		if (err) {
 			return callback(err);
 		}
diff --git a/src/routes/admin.js b/src/routes/admin.js
index f5d4d039a1..6ce4fb5f48 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -52,10 +52,11 @@ function addRoutes(router, middleware, controllers) {
 
 	router.get('/manage/categories', middlewares, controllers.admin.categories.getAll);
 	router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
+	router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics);
 
 	router.get('/manage/tags', middlewares, controllers.admin.tags.get);
-
 	router.get('/manage/flags', middlewares, controllers.admin.flags.get);
+	router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
 
 	router.get('/manage/users', middlewares, controllers.admin.users.sortByJoinDate);
 	router.get('/manage/users/search', middlewares, controllers.admin.users.search);
diff --git a/src/routes/authentication.js b/src/routes/authentication.js
index 37439ba523..8e1824cad6 100644
--- a/src/routes/authentication.js
+++ b/src/routes/authentication.js
@@ -62,8 +62,8 @@
 				}));
 			});
 
-			router.post('/register', Auth.middleware.applyCSRF, controllers.authentication.register);
-			router.post('/login', Auth.middleware.applyCSRF, controllers.authentication.login);
+			router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register);
+			router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login);
 			router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout);
 
 			hotswap.replace('auth', router);
diff --git a/src/routes/feeds.js b/src/routes/feeds.js
index b71fc99aab..bfa2945fb5 100644
--- a/src/routes/feeds.js
+++ b/src/routes/feeds.js
@@ -49,7 +49,7 @@ function generateForTopic(req, res, callback) {
 			return callback(err);
 		}
 
-		topics.modifyPostsByPrivilege(topicData.posts, userPrivileges);
+		topics.modifyPostsByPrivilege(topicData, userPrivileges);
 
 		var description = topicData.posts.length ? topicData.posts[0].content : '';
 		var image_url = topicData.posts.length ? topicData.posts[0].picture : '';
diff --git a/src/routes/index.js b/src/routes/index.js
index 7e84f29af1..a80eb2e46a 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -36,8 +36,9 @@ function mainRoutes(app, middleware, controllers) {
 	setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse);
 }
 
-function postRoutes(app, middleware, controllers) {
-	setupPageRoute(app, '/posts/flags', middleware, [], controllers.posts.flagged);
+function globalModRoutes(app, middleware, controllers) {
+	setupPageRoute(app, '/ip-blacklist', middleware, [], controllers.globalMods.ipBlacklist);
+	setupPageRoute(app, '/posts/flags', middleware, [], controllers.globalMods.flagged);
 }
 
 function topicRoutes(app, middleware, controllers) {
@@ -111,7 +112,7 @@ module.exports = function(app, middleware) {
 
 	mainRoutes(router, middleware, controllers);
 	topicRoutes(router, middleware, controllers);
-	postRoutes(router, middleware, controllers);
+	globalModRoutes(router, middleware, controllers);
 	tagRoutes(router, middleware, controllers);
 	categoryRoutes(router, middleware, controllers);
 	accountRoutes(router, middleware, controllers);
@@ -145,8 +146,8 @@ module.exports = function(app, middleware) {
 
 function handle404(app, middleware) {
 	var relativePath = nconf.get('relative_path');
-	var	isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'),
-		isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js');
+	var isLanguage = new RegExp('^' + relativePath + '/language/.*/.*.json');
+	var isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js');
 
 	app.use(function(req, res) {
 		if (plugins.hasListeners('action:meta.override404')) {
@@ -187,9 +188,12 @@ function handle404(app, middleware) {
 
 function handleErrors(app, middleware) {
 	app.use(function(err, req, res, next) {
-		if (err.code === 'EBADCSRFTOKEN') {
-			winston.error(req.path + '\n', err.message);
-			return res.sendStatus(403);
+		switch (err.code) {
+			case 'EBADCSRFTOKEN':
+				winston.error(req.path + '\n', err.message);
+				return res.sendStatus(403);
+			case 'blacklisted-ip':
+				return res.status(403).type('text/plain').send(err.message);
 		}
 
 		if (parseInt(err.status, 10) === 302 && err.path) {
diff --git a/src/search.js b/src/search.js
index ca457d8b0b..9b7306c7fa 100644
--- a/src/search.js
+++ b/src/search.js
@@ -85,7 +85,9 @@ function searchInContent(data, callback) {
 					topics.getMainPids(results.tids, next);
 				},
 				function(mainPids, next) {
-					results.pids = mainPids.concat(results.pids).filter(function(pid, index, array) {
+					results.pids = mainPids.concat(results.pids).map(function(pid) {
+						return pid && pid.toString();
+					}).filter(function(pid, index, array) {
 						return pid && array.indexOf(pid) === index;
 					});
 
diff --git a/src/settings.js b/src/settings.js
index 3d90dcfa84..6f6cad2854 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -3,16 +3,19 @@
 var meta = require('./meta');
 
 function expandObjBy(obj1, obj2) {
-	var key, val1, val2, changed = false;
+	var key, val1, val2, xorValIsArray, changed = false;
 	for (key in obj2) {
 		if (obj2.hasOwnProperty(key)) {
 			val2 = obj2[key];
 			val1 = obj1[key];
-			if (!obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) {
+			xorValIsArray = Array.isArray(val1) ^ Array.isArray(val2);
+			if (xorValIsArray || !obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) {
 				obj1[key] = val2;
 				changed = true;
-			} else if (typeof val2 === 'object' && expandObjBy(val1, val2)) {
-				changed = true;
+			} else if (typeof val2 === 'object' && !Array.isArray(val2)) {
+				if (expandObjBy(val1, val2)) {
+					changed = true;
+				}
 			}
 		}
 	}
@@ -26,10 +29,10 @@ function trim(obj1, obj2) {
 			val1 = obj1[key];
 			if (!obj2.hasOwnProperty(key)) {
 				delete obj1[key];
-			} else if (typeof val1 === 'object') {
+			} else if (typeof val1 === 'object' && !Array.isArray(val1)) {
 				trim(val1, obj2[key]);
 			}
-		}	
+		}
 	}
 }
 
diff --git a/src/social.js b/src/social.js
index c450421dcb..fec8fb036c 100644
--- a/src/social.js
+++ b/src/social.js
@@ -6,7 +6,13 @@ var async = require('async');
 
 var social = {};
 
+social.postSharing = null;
+
 social.getPostSharing = function(callback) {
+	if (social.postSharing) {
+		return callback(null, social.postSharing);
+	}
+
 	var networks = [
 		{
 			id: "facebook",
@@ -39,20 +45,41 @@ social.getPostSharing = function(callback) {
 					networks[i].activated = (activated.indexOf(network.id) !== -1);
 				});
 
+				social.postSharing = networks;
 				next(null, networks);
 			});
 		}
 	], callback);
 };
 
-social.setActivePostSharingNetworks = function(networkIDs, callback) {
-	db.delete('social:posts.activated', function(err) {
-		if (!networkIDs.length) {
+social.getActivePostSharing = function(callback) {
+	social.getPostSharing(function(err, networks) {
+		if (err) {
 			return callback(err);
 		}
-
-		db.setAdd('social:posts.activated', networkIDs, callback);
+		networks = networks.filter(function(network) {
+			return network && network.activated;
+		});
+		callback(null, networks);
 	});
 };
 
+social.setActivePostSharingNetworks = function(networkIDs, callback) {
+	async.waterfall([
+		function (next) {
+			db.delete('social:posts.activated', next);
+		},
+		function (next) {
+			if (!networkIDs.length) {
+				return next();
+			}
+			db.setAdd('social:posts.activated', networkIDs, next);
+		},
+		function (next) {
+			social.postSharing = null;
+			next();
+		}
+	], callback);
+};
+
 module.exports = social;
\ No newline at end of file
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 853d3f1536..6dd22134ba 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -8,7 +8,7 @@ var	async = require('async'),
 	plugins = require('../plugins'),
 	widgets = require('../widgets'),
 	user = require('../user'),
-	posts = require('../posts'),
+
 	logger = require('../logger'),
 	events = require('../events'),
 	emailer = require('../emailer'),
@@ -60,6 +60,7 @@ SocketAdmin.reload = function(socket, data, callback) {
 		process.send({
 			action: 'reload'
 		});
+		callback();
 	} else {
 		meta.reload(callback);
 	}
@@ -72,10 +73,12 @@ SocketAdmin.restart = function(socket, data, callback) {
 		ip: socket.ip
 	});
 	meta.restart();
+	callback();
 };
 
 SocketAdmin.fireEvent = function(socket, data, callback) {
 	index.server.emit(data.name, data.payload || {});
+	callback();
 };
 
 SocketAdmin.themes.getInstalled = function(socket, data, callback) {
@@ -83,7 +86,7 @@ SocketAdmin.themes.getInstalled = function(socket, data, callback) {
 };
 
 SocketAdmin.themes.set = function(socket, data, callback) {
-	if(!data) {
+	if (!data) {
 		return callback(new Error('[[error:invalid-data]]'));
 	}
 
@@ -134,16 +137,16 @@ SocketAdmin.widgets.set = function(socket, data, callback) {
 };
 
 SocketAdmin.config.set = function(socket, data, callback) {
-	if(!data) {
+	if (!data) {
 		return callback(new Error('[[error:invalid-data]]'));
 	}
 
 	meta.configs.set(data.key, data.value, function(err) {
-		if(err) {
+		if (err) {
 			return callback(err);
 		}
 
-		callback(null);
+		callback();
 
 		plugins.fireHook('action:config.set', {
 			key: data.key,
@@ -155,7 +158,7 @@ SocketAdmin.config.set = function(socket, data, callback) {
 };
 
 SocketAdmin.config.setMultiple = function(socket, data, callback) {
-	if(!data) {
+	if (!data) {
 		return callback(new Error('[[error:invalid-data]]'));
 	}
 
@@ -179,8 +182,9 @@ SocketAdmin.config.setMultiple = function(socket, data, callback) {
 	});
 };
 
-SocketAdmin.config.remove = function(socket, key) {
+SocketAdmin.config.remove = function(socket, key, callback) {
 	meta.configs.remove(key);
+	callback();
 };
 
 SocketAdmin.settings.get = function(socket, data, callback) {
diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js
index 288e395837..3b35847366 100644
--- a/src/socket.io/admin/categories.js
+++ b/src/socket.io/admin/categories.js
@@ -1,16 +1,16 @@
 "use strict";
 
-var async = require('async'),
+var async = require('async');
 
-	db = require('../../database'),
-	groups = require('../../groups'),
-	categories = require('../../categories'),
-	privileges = require('../../privileges'),
-	plugins = require('../../plugins'),
-	Categories = {};
+var db = require('../../database');
+var groups = require('../../groups');
+var categories = require('../../categories');
+var privileges = require('../../privileges');
+var plugins = require('../../plugins');
+var Categories = {};
 
 Categories.create = function(socket, data, callback) {
-	if(!data) {
+	if (!data) {
 		return callback(new Error('[[error:invalid-data]]'));
 	}
 
@@ -46,7 +46,7 @@ Categories.purge = function(socket, cid, callback) {
 };
 
 Categories.update = function(socket, data, callback) {
-	if(!data) {
+	if (!data) {
 		return callback(new Error('[[error:invalid-data]]'));
 	}
 
@@ -108,4 +108,8 @@ function copyPrivilegesToChildrenRecursive(category, privilegeGroups, callback)
 	});
 }
 
+Categories.copySettingsFrom = function(socket, data, callback) {
+	categories.copySettingsFrom(data.fromCid, data.toCid, callback);
+};
+
 module.exports = Categories;
\ No newline at end of file
diff --git a/src/socket.io/admin/rooms.js b/src/socket.io/admin/rooms.js
index bf66873a52..50ba4661de 100644
--- a/src/socket.io/admin/rooms.js
+++ b/src/socket.io/admin/rooms.js
@@ -8,9 +8,13 @@ var validator = require('validator');
 var topics = require('../../topics');
 var pubsub = require('../../pubsub');
 
-var SocketRooms = {};
-
 var stats = {};
+var totals = {};
+var SocketRooms = {
+	stats: stats,
+	totals: totals
+};
+
 
 pubsub.on('sync:stats:start', function() {
 	getLocalStats(function(err, stats) {
@@ -25,20 +29,43 @@ pubsub.on('sync:stats:end', function(data) {
 	stats[data.id] = data.stats;
 });
 
+pubsub.on('sync:stats:guests', function() {
+	var io = require('../index').server;
+
+	var roomClients = io.sockets.adapter.rooms;
+	var guestCount = roomClients.online_guests ? roomClients.online_guests.length : 0;
+	pubsub.publish('sync:stats:guests:end', guestCount);
+});
+
+SocketRooms.getTotalGuestCount = function(callback) {
+	var count = 0;
+
+	pubsub.on('sync:stats:guests:end', function(guestCount) {
+		count += guestCount;
+	});
+
+	pubsub.publish('sync:stats:guests');
+
+	setTimeout(function() {
+		pubsub.removeAllListeners('sync:stats:guests:end');
+		callback(null, count);
+	}, 100);
+}
+
+
 SocketRooms.getAll = function(socket, data, callback) {
 	pubsub.publish('sync:stats:start');
-	var totals = {
-		onlineGuestCount: 0,
-		onlineRegisteredCount: 0,
-		socketCount: 0,
-		users: {
-			categories: 0,
-			recent: 0,
-			unread: 0,
-			topics: 0,
-			category: 0
-		},
-		topics: {}
+
+	totals.onlineGuestCount = 0;
+	totals.onlineRegisteredCount = 0;
+	totals.socketCount = 0;
+	totals.topics = {};
+	totals.users = {
+		categories: 0,
+		recent: 0,
+		unread: 0,
+		topics: 0,
+		category: 0
 	};
 
 	for(var instance in stats) {
@@ -88,45 +115,53 @@ SocketRooms.getAll = function(socket, data, callback) {
 	});
 };
 
-SocketRooms.getStats = function() {
-	return stats;
+SocketRooms.getOnlineUserCount = function(io) {
+	if (!io) {
+		return 0;
+	}
+	var count = 0;
+	for (var key in io.sockets.adapter.rooms) {
+		if (io.sockets.adapter.rooms.hasOwnProperty(key) && key.startsWith('uid_')) {
+			++ count;
+		}
+	}
+
+	return count;
 };
 
 function getLocalStats(callback) {
-	var websockets = require('../index');
-	var io = websockets.server;
+	var io = require('../index').server;
+
 	if (!io) {
 		return callback();
 	}
 
 	var roomClients = io.sockets.adapter.rooms;
 	var socketData = {
-		onlineGuestCount: websockets.getOnlineAnonCount(),
-		onlineRegisteredCount: websockets.getOnlineUserCount(),
-		socketCount: websockets.getSocketCount(),
+		onlineGuestCount: roomClients.online_guests ? roomClients.online_guests.length : 0,
+		onlineRegisteredCount: SocketRooms.getOnlineUserCount(io),
+		socketCount: Object.keys(io.sockets.sockets).length,
 		users: {
-			categories: roomClients.categories ? Object.keys(roomClients.categories).length : 0,
-			recent: roomClients.recent_topics ? Object.keys(roomClients.recent_topics).length : 0,
-			unread: roomClients.unread_topics ? Object.keys(roomClients.unread_topics).length: 0,
+			categories: roomClients.categories ? roomClients.categories.length : 0,
+			recent: roomClients.recent_topics ? roomClients.recent_topics.length : 0,
+			unread: roomClients.unread_topics ? roomClients.unread_topics.length: 0,
 			topics: 0,
 			category: 0
 		},
 		topics: {}
 	};
 
-	var	topTenTopics = [],
-		tid;
+	var	topTenTopics = [];
+	var tid;
 
 	for (var room in roomClients) {
 		if (roomClients.hasOwnProperty(room)) {
 			tid = room.match(/^topic_(\d+)/);
 			if (tid) {
-				var length = Object.keys(roomClients[room]).length;
-				socketData.users.topics += length;
-
-				topTenTopics.push({tid: tid[1], count: length});
+				socketData.users.topics += roomClients[room].length;
+				topTenTopics.push({tid: tid[1], count: roomClients[room].length});
 			} else if (room.match(/^category/)) {
-				socketData.users.category += Object.keys(roomClients[room]).length;
+				socketData.users.category += roomClients[room].length;
 			}
 		}
 	}
diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js
index a241affb90..2633425dc4 100644
--- a/src/socket.io/admin/user.js
+++ b/src/socket.io/admin/user.js
@@ -156,7 +156,7 @@ User.deleteUsers = function(socket, uids, callback) {
 					return next(new Error('[[error:cant-delete-other-admins]]'));
 				}
 
-				user.delete(uid, next);
+				user.delete(socket.uid, uid, next);
 			},
 			function (next) {
 				events.log({
@@ -212,6 +212,10 @@ User.search = function(socket, data, callback) {
 	});
 };
 
+User.deleteInvitation = function(socket, data, callback) {
+	user.deleteInvitation(data.invitedBy, data.email, callback);
+};
+
 User.acceptRegistration = function(socket, data, callback) {
 	user.acceptRegistration(data.username, callback);
 };
@@ -221,4 +225,4 @@ User.rejectRegistration = function(socket, data, callback) {
 };
 
 
-module.exports = User;
\ No newline at end of file
+module.exports = User;
diff --git a/src/socket.io/blacklist.js b/src/socket.io/blacklist.js
new file mode 100644
index 0000000000..f4158dc94b
--- /dev/null
+++ b/src/socket.io/blacklist.js
@@ -0,0 +1,27 @@
+
+'use strict';
+
+var async = require('async');
+var winston = require('winston');
+
+var user = require('../user');
+var meta = require('../meta');
+
+var SocketBlacklist = {};
+
+SocketBlacklist.validate = function(socket, data, callback) {
+	meta.blacklist.validate(data.rules, callback);
+};
+
+SocketBlacklist.save = function(socket, rules, callback) {
+	user.isAdminOrGlobalMod(socket.uid, function(err, isAdminOrGlobalMod) {
+		if (err || !isAdminOrGlobalMod) {
+			return callback(err || new Error('[[error:no-privileges]]'));
+		}
+
+		meta.blacklist.save(rules, callback);
+	});
+};
+
+
+module.exports = SocketBlacklist;
diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js
index 7e8b6000f3..262a5e8a41 100644
--- a/src/socket.io/categories.js
+++ b/src/socket.io/categories.js
@@ -6,7 +6,7 @@ var categories = require('../categories');
 var privileges = require('../privileges');
 var user = require('../user');
 var topics = require('../topics');
-
+var apiController = require('../controllers/api');
 
 var SocketCategories = {};
 
@@ -192,4 +192,8 @@ SocketCategories.isModerator = function(socket, cid, callback) {
 	user.isModerator(socket.uid, cid, callback);
 };
 
+SocketCategories.getCategory = function(socket, cid, callback) {
+	apiController.getObjectByType(socket.uid, 'category', cid, callback);
+};
+
 module.exports = SocketCategories;
diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js
index dc8ec8c51a..a54db76b05 100644
--- a/src/socket.io/groups.js
+++ b/src/socket.io/groups.js
@@ -155,9 +155,15 @@ SocketGroups.kick = isOwner(function(socket, data, callback) {
 	if (socket.uid === parseInt(data.uid, 10)) {
 		return callback(new Error('[[error:cant-kick-self]]'));
 	}
-	groups.leave(data.groupName, data.uid, callback);
-});
 
+	groups.ownership.isOwner(data.uid, data.groupName, function(err, isOwner) {
+		if (err) {
+			return callback(err);
+		}
+		groups.kick(data.uid, data.groupName, isOwner, callback);
+	});
+
+});
 
 SocketGroups.create = function(socket, data, callback) {
 	if (!socket.uid) {
@@ -172,16 +178,16 @@ SocketGroups.create = function(socket, data, callback) {
 };
 
 SocketGroups.delete = function(socket, data, callback) {
-	if (data.groupName === 'administrators' || data.groupName === 'registered-users') {
+	if (data.groupName === 'administrators' ||
+		data.groupName === 'registered-users' ||
+		data.groupName === 'Global Moderators') {
 		return callback(new Error('[[error:not-allowed]]'));
 	}
 
-	var tasks = {
+	async.parallel({
 		isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName),
 		isAdmin: async.apply(user.isAdministrator, socket.uid)
-	};
-
-	async.parallel(tasks, function(err, checks) {
+	}, function(err, checks) {
 		if (err) {
 			return callback(err);
 		}
diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js
index 3aeaaf0bf3..12a0e9d9b5 100644
--- a/src/socket.io/helpers.js
+++ b/src/socket.io/helpers.js
@@ -2,6 +2,7 @@
 
 var async = require('async');
 var winston = require('winston');
+var S = require('string');
 var nconf = require('nconf');
 
 var websockets = require('./index');
@@ -15,6 +16,11 @@ var plugins = require('../plugins');
 var SocketHelpers = {};
 
 SocketHelpers.notifyOnlineUsers = function(uid, result) {
+	winston.warn('[deprecated] SocketHelpers.notifyOnlineUsers, consider using socketHelpers.notifyNew(uid, \'newPost\', result);');
+	SocketHelpers.notifyNew(uid, 'newPost', result);
+};
+
+SocketHelpers.notifyNew = function(uid, type, result) {
 	async.waterfall([
 		function(next) {
 			user.getUidsFromSet('users:online', 0, -1, next);
@@ -23,20 +29,23 @@ SocketHelpers.notifyOnlineUsers = function(uid, result) {
 			privileges.topics.filterUids('read', result.posts[0].topic.tid, uids, next);
 		},
 		function(uids, next) {
-			plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: uid, type: 'newPost'}, next);
+			plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: uid, type: type}, next);
 		}
 	], function(err, data) {
 		if (err) {
 			return winston.error(err.stack);
 		}
 
-		var uids = data.uidsTo;
+		result.posts[0].ip = undefined;
 
-		for(var i=0; i<uids.length; ++i) {
-			if (parseInt(uids[i], 10) !== uid) {
-				websockets.in('uid_' + uids[i]).emit('event:new_post', result);
+		data.uidsTo.forEach(function(toUid) {
+			if (parseInt(toUid, 10) !== uid) {
+				websockets.in('uid_' + toUid).emit('event:new_post', result);
+				if (result.topic && type === 'newTopic') {
+					websockets.in('uid_' + toUid).emit('event:new_topic', result.topic);
+				}
 			}
-		}
+		});
 	});
 };
 
@@ -62,8 +71,11 @@ SocketHelpers.sendNotificationToPostOwner = function(pid, fromuid, notification)
 				return;
 			}
 
+			var title = S(results.topicTitle).decodeHTMLEntities().s;
+			var titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
+
 			notifications.create({
-				bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicTitle + ']]',
+				bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]',
 				bodyLong: results.postObj.content,
 				pid: pid,
 				nid: 'post:' + pid + ':uid:' + fromuid,
@@ -93,8 +105,11 @@ SocketHelpers.sendNotificationToTopicOwner = function(tid, fromuid, notification
 			return;
 		}
 
+		var title = S(results.topicData.title).decodeHTMLEntities().s;
+		var titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
+
 		notifications.create({
-			bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicData.title + ']]',
+			bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]',
 			path: nconf.get('relative_path') + '/topic/' + results.topicData.slug,
 			nid: 'tid:' + tid + ':uid:' + fromuid,
 			from: fromuid
@@ -111,4 +126,4 @@ SocketHelpers.emitToTopicAndCategory = function(event, data) {
 	websockets.in('category_' + data.cid).emit(event, data);
 };
 
-module.exports = SocketHelpers;
\ No newline at end of file
+module.exports = SocketHelpers;
diff --git a/src/socket.io/index.js b/src/socket.io/index.js
index a67ca29869..8f6f3c4c5a 100644
--- a/src/socket.io/index.js
+++ b/src/socket.io/index.js
@@ -8,7 +8,6 @@ var cookieParser = require('cookie-parser')(nconf.get('secret'));
 var winston = require('winston');
 
 var db = require('../database');
-var user = require('../user');
 var logger = require('../logger');
 var ratelimit = require('../middleware/ratelimit');
 
@@ -45,10 +44,6 @@ function onConnection(socket) {
 
 	onConnect(socket);
 
-	socket.on('disconnect', function(data) {
-		onDisconnect(socket, data);
-	});
-
 	socket.on('*', function(payload) {
 		onMessage(socket, payload);
 	});
@@ -58,29 +53,11 @@ function onConnect(socket) {
 	if (socket.uid) {
 		socket.join('uid_' + socket.uid);
 		socket.join('online_users');
-
-		user.getUserFields(socket.uid, ['status'], function(err, userData) {
-			if (err || !userData) {
-				return;
-			}
-
-			if (userData.status !== 'offline') {
-				socket.broadcast.emit('event:user_status_change', {uid: socket.uid, status: userData.status || 'online'});
-			}
-		});
 	} else {
 		socket.join('online_guests');
 	}
 }
 
-function onDisconnect(socket) {
-	if (socket.uid) {
-		var socketCount = Sockets.getUserSocketCount(socket.uid);
-		if (socketCount <= 1) {
-			socket.broadcast.emit('event:user_status_change', {uid: socket.uid, status: 'offline'});
-		}
-	}
-}
 
 function onMessage(socket, payload) {
 	if (!payload.data.length) {
@@ -144,7 +121,7 @@ function onMessage(socket, payload) {
 
 function requireModules() {
 	var modules = ['admin', 'categories', 'groups', 'meta', 'modules',
-		'notifications', 'plugins', 'posts', 'topics', 'user'
+		'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist'
 	];
 
 	modules.forEach(function(module) {
@@ -183,6 +160,7 @@ function authorize(socket, callback) {
 					return next(err);
 				}
 				if (sessionData && sessionData.passport && sessionData.passport.user) {
+					request.session = sessionData;
 					socket.uid = parseInt(sessionData.passport.user, 10);
 				} else {
 					socket.uid = 0;
@@ -210,37 +188,15 @@ Sockets.in = function(room) {
 	return io.in(room);
 };
 
-Sockets.getSocketCount = function() {
-	if (!io) {
-		return 0;
-	}
-
-	return Object.keys(io.sockets.sockets).length;
-};
-
 Sockets.getUserSocketCount = function(uid) {
 	if (!io) {
 		return 0;
 	}
-	var room = io.sockets.adapter.rooms['uid_' + uid];
-	return room ? room.length : 0;
-};
 
-Sockets.getOnlineUserCount = function() {
-	if (!io) {
-		return 0;
-	}
-	var room = io.sockets.adapter.rooms.online_users;
+	var room = io.sockets.adapter.rooms['uid_' + uid];
 	return room ? room.length : 0;
 };
 
-Sockets.getOnlineAnonCount = function () {
-	if (!io) {
-		return 0;
-	}
-	var room = io.sockets.adapter.rooms.online_guests;
-	return room ? room.length : 0;
-};
 
 Sockets.reqFromSocket = function(socket) {
 	var headers = socket.request.headers;
@@ -258,33 +214,5 @@ Sockets.reqFromSocket = function(socket) {
 	};
 };
 
-Sockets.isUserOnline = function(uid) {
-	winston.warn('[deprecated] Sockets.isUserOnline');
-	return false;
-};
-
-Sockets.isUsersOnline = function(uids, callback) {
-	winston.warn('[deprecated] Sockets.isUsersOnline');
-	callback(null, uids.map(function() { return false; }));
-};
-
-Sockets.getUsersInRoom = function (uid, roomName, start, stop, callback) {
-	winston.warn('[deprecated] Sockets.getUsersInRoom');
-	callback(null, {
-		users: [],
-		room: roomName,
-		total: 0,
-		hidden: 0
-	});
-	return;
-};
-
-Sockets.getUidsInRoom = function(roomName, callback) {
-	winston.warn('[deprecated] Sockets.getUidsInRoom');
-	callback = callback || function() {};
-	callback(null, []);
-};
-
 
-/* Exporting */
 module.exports = Sockets;
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index 26426b9f9f..ebd27730e4 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -61,9 +61,9 @@ SocketModules.chats.newRoom = function(socket, data, callback) {
 		socket.lastChatMessageTime = now;
 	}
 
-	Messaging.canMessageUser(socket.uid, data.touid, function(err, allowed) {
-		if (err || !allowed) {
-			return callback(err || new Error('[[error:chat-restricted]]'));
+	Messaging.canMessageUser(socket.uid, data.touid, function(err) {
+		if (err) {
+			return callback(err);
 		}
 
 		Messaging.newRoom(socket.uid, [data.touid], callback);
@@ -240,10 +240,23 @@ SocketModules.chats.markRead = function(socket, roomId, callback) {
 			user.notifications.pushCount(socket.uid);
 		});
 
+		server.in('uid_' + socket.uid).emit('event:chats.markedAsRead', {roomId: roomId});
 		callback();
 	});
 };
 
+SocketModules.chats.markAllRead = function(socket, data, callback) {
+	async.waterfall([
+		function (next) {
+			Messaging.markAllRead(socket.uid, next);
+		},
+		function (next) {
+			Messaging.pushUnreadCount(socket.uid);
+			next();
+		}
+	], callback);
+};
+
 SocketModules.chats.renameRoom = function(socket, data, callback) {
 	if (!data) {
 		return callback(new Error('[[error:invalid-name]]'));
diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js
index 7fc25141bb..121ede2a96 100644
--- a/src/socket.io/notifications.js
+++ b/src/socket.io/notifications.js
@@ -3,6 +3,7 @@
 var async = require('async');
 var user = require('../user');
 var notifications = require('../notifications');
+var utils = require('../../public/src/utils');
 
 var SocketNotifs = {};
 
@@ -15,11 +16,11 @@ SocketNotifs.get = function(socket, data, callback) {
 };
 
 SocketNotifs.loadMore = function(socket, data, callback) {
-	if (!data || !parseInt(data.after, 10)) {
+	if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
 		return callback(new Error('[[error:invalid-data]]'));
 	}
 	if (!socket.uid) {
-		return;
+		return callback(new Error('[[error:no-privileges]]'));
 	}
 	var start = parseInt(data.after, 10);
 	var stop = start + 20;
@@ -37,7 +38,7 @@ SocketNotifs.getCount = function(socket, data, callback) {
 
 SocketNotifs.deleteAll = function(socket, data, callback) {
 	if (!socket.uid) {
-		return;
+		return callback(new Error('[[error:no-privileges]]'));
 	}
 
 	user.notifications.deleteAll(socket.uid, callback);
@@ -57,7 +58,7 @@ SocketNotifs.markAllRead = function(socket, data, callback) {
 
 SocketNotifs.generatePath = function(socket, nid, callback) {
 	if (!socket.uid) {
-		return;
+		return callback(new Error('[[error:no-privileges]]'));;
 	}
 	async.waterfall([
 		function (next) {
diff --git a/src/socket.io/plugins.js b/src/socket.io/plugins.js
index aad0bb2841..32f1e7f00f 100644
--- a/src/socket.io/plugins.js
+++ b/src/socket.io/plugins.js
@@ -9,7 +9,7 @@ var	SocketPlugins = {};
 
 		var SocketPlugins = require.main.require('./src/socket.io/plugins');
 		SocketPlugins.myPlugin = {};
-		SocketPlugins.myPlugin.myMethod = function() { ... };
+		SocketPlugins.myPlugin.myMethod = function(socket, data, callback) { ... };
 
 	Be a good lad and namespace your methods.
 */
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 64b5b75076..5e31e0c611 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -1,18 +1,20 @@
 "use strict";
 
-var	async = require('async'),
+var	async = require('async');
 
-	posts = require('../posts'),
-	privileges = require('../privileges'),
-	meta = require('../meta'),
-	topics = require('../topics'),
-	user = require('../user'),
-	websockets = require('./index'),
-	socketTopics = require('./topics'),
-	socketHelpers = require('./helpers'),
-	utils = require('../../public/src/utils'),
+var posts = require('../posts');
+var privileges = require('../privileges');
+var meta = require('../meta');
+var topics = require('../topics');
+var user = require('../user');
+var websockets = require('./index');
+var socketTopics = require('./topics');
+var socketHelpers = require('./helpers');
+var utils = require('../../public/src/utils');
 
-	SocketPosts = {};
+var apiController = require('../controllers/api');
+
+var SocketPosts = {};
 
 
 require('./posts/edit')(SocketPosts);
@@ -49,7 +51,7 @@ SocketPosts.reply = function(socket, data, callback) {
 
 		user.updateOnlineUsers(socket.uid);
 
-		socketHelpers.notifyOnlineUsers(socket.uid, result);
+		socketHelpers.notifyNew(socket.uid, 'newPost', result);
 
 		if (data.lock) {
 			socketTopics.doTopicAction('lock', 'event:topic_locked', socket, {tids: [postData.topic.tid], cid: postData.topic.cid});
@@ -77,6 +79,20 @@ SocketPosts.getRawPost = function(socket, pid, callback) {
 	], callback);
 };
 
+SocketPosts.getPost = function(socket, pid, callback) {
+	async.waterfall([
+		function(next) {
+			apiController.getObjectByType(socket.uid, 'post', pid, next);
+		},
+		function(postData, next) {
+			if (parseInt(postData.deleted, 10) === 1) {
+				return next(new Error('[[error:no-post]]'));
+			}
+			next(null, postData);
+		}
+	], callback);
+};
+
 SocketPosts.loadMoreFavourites = function(socket, data, callback) {
 	loadMorePosts('uid:' + data.uid + ':favourites', socket.uid, data, callback);
 };
@@ -119,4 +135,6 @@ SocketPosts.getPidIndex = function(socket, data, callback) {
 	posts.getPidIndex(data.pid, data.tid, data.topicPostSort, callback);
 };
 
+
+
 module.exports = SocketPosts;
diff --git a/src/socket.io/posts/edit.js b/src/socket.io/posts/edit.js
index bec03451fb..e5ad7a0afb 100644
--- a/src/socket.io/posts/edit.js
+++ b/src/socket.io/posts/edit.js
@@ -37,7 +37,8 @@ module.exports = function(SocketPosts) {
 			title: data.title,
 			content: data.content,
 			topic_thumb: data.topic_thumb,
-			tags: data.tags
+			tags: data.tags,
+			req: websockets.reqFromSocket(socket)
 		}, function(err, result) {
 			if (err) {
 				return callback(err);
diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js
index eaefcea1e2..bbf7a4721c 100644
--- a/src/socket.io/posts/flag.js
+++ b/src/socket.io/posts/flag.js
@@ -1,6 +1,7 @@
 'use strict';
 
 var async = require('async');
+var S = require('string');
 
 var user = require('../../user');
 var groups = require('../../groups');
@@ -82,8 +83,11 @@ module.exports = function(SocketPosts) {
 				}, next);
 			},
 			function (results, next) {
+				var title = S(post.topic.title).decodeHTMLEntities().s;
+				var titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
+
 				notifications.create({
-					bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + post.topic.title + ']]',
+					bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + titleEscaped + ']]',
 					bodyLong: post.content,
 					pid: data.pid,
 					nid: 'post_flag:' + data.pid + ':uid:' + socket.uid,
@@ -163,4 +167,4 @@ module.exports = function(SocketPosts) {
 			},
 		], callback);
 	};
-};
\ No newline at end of file
+};
diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js
index c3f08461fe..5c59b28918 100644
--- a/src/socket.io/posts/tools.js
+++ b/src/socket.io/posts/tools.js
@@ -32,7 +32,7 @@ module.exports = function(SocketPosts) {
 				plugins.fireHook('filter:post.tools', {pid: data.pid, uid: socket.uid, tools: []}, next);
 			},
 			postSharing: function(next) {
-				social.getPostSharing(next);
+				social.getActivePostSharing(next);
 			}
 		}, function(err, results) {
 			if (err) {
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index 940ed0a470..c96a77265e 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -1,18 +1,15 @@
 
 'use strict';
 
-var nconf = require('nconf'),
-	async = require('async'),
-	winston = require('winston'),
+var async = require('async');
 
-	topics = require('../topics'),
-	privileges = require('../privileges'),
-	plugins = require('../plugins'),
-	notifications = require('../notifications'),
-	websockets = require('./index'),
-	user = require('../user'),
+var topics = require('../topics');
+var websockets = require('./index');
+var user = require('../user');
+var apiController = require('../controllers/api');
+var socketHelpers = require('./helpers');
 
-	SocketTopics = {};
+var SocketTopics = {};
 
 require('./topics/unread')(SocketTopics);
 require('./topics/move')(SocketTopics);
@@ -44,33 +41,11 @@ SocketTopics.post = function(socket, data, callback) {
 		}
 
 		callback(null, result.topicData);
+
 		socket.emit('event:new_post', {posts: [result.postData]});
 		socket.emit('event:new_topic', result.topicData);
 
-		async.waterfall([
-			function(next) {
-				user.getUidsFromSet('users:online', 0, -1, next);
-			},
-			function(uids, next) {
-				privileges.categories.filterUids('read', result.topicData.cid, uids, next);
-			},
-			function(uids, next) {
-				plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: data.uid, type: 'newTopic'}, next);
-			}
-		], function(err, data) {
-			if (err) {
-				return winston.error(err.stack);
-			}
-
-			var uids = data.uidsTo;
-
-			for(var i=0; i<uids.length; ++i) {
-				if (parseInt(uids[i], 10) !== socket.uid) {
-					websockets.in('uid_' + uids[i]).emit('event:new_post', {posts: [result.postData]});
-					websockets.in('uid_' + uids[i]).emit('event:new_topic', result.topicData);
-				}
-			}
-		});
+		socketHelpers.notifyNew(socket.uid, 'newTopic', {posts: [result.postData], topic: result.topicData});
 	});
 };
 
@@ -126,4 +101,18 @@ SocketTopics.isModerator = function(socket, tid, callback) {
 	});
 };
 
+SocketTopics.getTopic = function (socket, tid, callback) {
+	async.waterfall([
+		function (next) {
+			apiController.getObjectByType(socket.uid, 'topic', tid, next);
+		},
+		function (topicData, next) {
+			if (parseInt(topicData.deleted, 10) === 1) {
+				return next(new Error('[[error:no-topic]]'));
+			}
+			next(null, topicData);
+		}
+	], callback);
+};
+
 module.exports = SocketTopics;
diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js
index 8cb975c15e..5c45c6e4fb 100644
--- a/src/socket.io/topics/infinitescroll.js
+++ b/src/socket.io/topics/infinitescroll.js
@@ -6,6 +6,7 @@ var topics = require('../../topics');
 var privileges = require('../../privileges');
 var meta = require('../../meta');
 var utils = require('../../../public/src/utils');
+var social = require('../../social');
 
 module.exports = function(SocketTopics) {
 
@@ -68,6 +69,9 @@ module.exports = function(SocketTopics) {
 				},
 				posts: function(next) {
 					topics.getTopicPosts(data.tid, set, start, stop, socket.uid, reverse, next);
+				},
+				postSharing: function (next) {
+					social.getActivePostSharing(next);
 				}
 			}, function(err, topicData) {
 				if (err) {
@@ -81,14 +85,14 @@ module.exports = function(SocketTopics) {
 				topicData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
 				topicData['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
 
-				topics.modifyPostsByPrivilege(topicData.posts, results.privileges);
+				topics.modifyPostsByPrivilege(topicData, results.privileges);
 				callback(null, topicData);
 			});
 		});
 	};
 
 	SocketTopics.loadMoreUnreadTopics = function(socket, data, callback) {
-		if (!data || !data.after) {
+		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
 			return callback(new Error('[[error:invalid-data]]'));
 		}
 
@@ -99,7 +103,7 @@ module.exports = function(SocketTopics) {
 	};
 
 	SocketTopics.loadMoreFromSet = function(socket, data, callback) {
-		if (!data || !data.after || !data.set) {
+		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) {
 			return callback(new Error('[[error:invalid-data]]'));
 		}
 
diff --git a/src/socket.io/topics/tags.js b/src/socket.io/topics/tags.js
index 535fdc3027..f55ec377fb 100644
--- a/src/socket.io/topics/tags.js
+++ b/src/socket.io/topics/tags.js
@@ -20,14 +20,16 @@ module.exports = function(SocketTopics) {
 			return callback(new Error('[[error:invalid-data]]'));
 		}
 
-		var start = parseInt(data.after, 10),
-			stop = start + 99;
+		var start = parseInt(data.after, 10);
+		var stop = start + 99;
 
 		topics.getTags(start, stop, function(err, tags) {
 			if (err) {
 				return callback(err);
 			}
-
+			tags = tags.filter(function(tag) {
+				return tag && tag.score > 0;
+			});
 			callback(null, {tags: tags, nextStart: stop + 1});
 		});
 	};
diff --git a/src/socket.io/topics/tools.js b/src/socket.io/topics/tools.js
index 35e1118986..f3d9ad4688 100644
--- a/src/socket.io/topics/tools.js
+++ b/src/socket.io/topics/tools.js
@@ -11,7 +11,7 @@ module.exports = function(SocketTopics) {
 
 	SocketTopics.loadTopicTools = function(socket, data, callback) {
 		if (!socket.uid) {
-			return;
+			return callback(new Error('[[error:no-privileges]]'));
 		}
 		if (!data) {
 			return callback(new Error('[[error:invalid-data]]'));
@@ -74,7 +74,7 @@ module.exports = function(SocketTopics) {
 	SocketTopics.doTopicAction = function(action, event, socket, data, callback) {
 		callback = callback || function() {};
 		if (!socket.uid) {
-			return;
+			return callback(new Error('[[error:no-privileges]]'));
 		}
 
 		if (!data || !Array.isArray(data.tids) || !data.cid) {
diff --git a/src/socket.io/topics/unread.js b/src/socket.io/topics/unread.js
index 57eeb7958d..056b80b041 100644
--- a/src/socket.io/topics/unread.js
+++ b/src/socket.io/topics/unread.js
@@ -22,6 +22,7 @@ module.exports = function(SocketTopics) {
 			for (var i=0; i<tids.length; ++i) {
 				topics.markTopicNotificationsRead(tids[i], socket.uid);
 			}
+			callback();
 		});
 	};
 
diff --git a/src/socket.io/user.js b/src/socket.io/user.js
index be884d4c54..06acacd520 100644
--- a/src/socket.io/user.js
+++ b/src/socket.io/user.js
@@ -12,6 +12,7 @@ var meta = require('../meta');
 var events = require('../events');
 var emailer = require('../emailer');
 var db = require('../database');
+var apiController = require('../controllers/api');
 
 var SocketUser = {};
 
@@ -22,14 +23,15 @@ require('./user/picture')(SocketUser);
 require('./user/ban')(SocketUser);
 
 SocketUser.exists = function(socket, data, callback) {
-	if (data && data.username) {
-		meta.userOrGroupExists(data.username, callback);
+	if (!data || !data.username) {
+		return callback(new Error('[[error:invalid-data]]'));
 	}
+	meta.userOrGroupExists(data.username, callback);
 };
 
 SocketUser.deleteAccount = function(socket, data, callback) {
 	if (!socket.uid) {
-		return;
+		return callback(new Error('[[error:no-privileges]]'));
 	}
 
 	async.waterfall([
@@ -57,25 +59,27 @@ SocketUser.deleteAccount = function(socket, data, callback) {
 };
 
 SocketUser.emailExists = function(socket, data, callback) {
-	if (data && data.email) {
-		user.email.exists(data.email, callback);
+	if (!data || !data.email) {
+		return callback(new Error('[[error:invalid-data]]'));
 	}
+	user.email.exists(data.email, callback);
 };
 
 SocketUser.emailConfirm = function(socket, data, callback) {
-	if (socket.uid && parseInt(meta.config.requireEmailConfirmation, 10) === 1) {
-		user.getUserField(socket.uid, 'email', function(err, email) {
-			if (err) {
-				return callback(err);
-			}
-
-			if (!email) {
-				return;
-			}
+	if (!socket.uid) {
+		return callback(new Error('[[error:no-privileges]]'));
+	}
 
-			user.email.sendValidationEmail(socket.uid, email, callback);
-		});
+	if (parseInt(meta.config.requireEmailConfirmation, 10) !== 1) {
+		callback();
 	}
+	user.getUserField(socket.uid, 'email', function(err, email) {
+		if (err || !email) {
+			return callback(err);
+		}
+
+		user.email.sendValidationEmail(socket.uid, email, callback);
+	});
 };
 
 
@@ -83,9 +87,11 @@ SocketUser.emailConfirm = function(socket, data, callback) {
 SocketUser.reset = {};
 
 SocketUser.reset.send = function(socket, email, callback) {
-	if (email) {
-		user.reset.send(email, callback);
+	if (!email) {
+		return callback(new Error('[[error:invalid-data]]'));
 	}
+
+	user.reset.send(email, callback);
 };
 
 SocketUser.reset.commit = function(socket, data, callback) {
@@ -101,9 +107,9 @@ SocketUser.reset.commit = function(socket, data, callback) {
 			return callback(err);
 		}
 
-		var uid = results.uid,
-			now = new Date(),
-			parsedDate = now.getFullYear() + '/' + (now.getMonth()+1) + '/' + now.getDate();
+		var uid = results.uid;
+		var now = new Date();
+		var parsedDate = now.getFullYear() + '/' + (now.getMonth()+1) + '/' + now.getDate();
 
 		user.getUserField(uid, 'username', function(err, username) {
 			emailer.send('reset_notify', uid, {
@@ -133,7 +139,7 @@ SocketUser.isFollowing = function(socket, data, callback) {
 
 SocketUser.follow = function(socket, data, callback) {
 	if (!socket.uid || !data) {
-		return;
+		return callback(new Error('[[error:invalid-data]]'));
 	}
 	var userData;
 	async.waterfall([
@@ -164,9 +170,10 @@ SocketUser.follow = function(socket, data, callback) {
 };
 
 SocketUser.unfollow = function(socket, data, callback) {
-	if (socket.uid && data) {
-		toggleFollow('unfollow', socket.uid, data.uid, callback);
+	if (!socket.uid || !data) {
+		return callback(new Error('[[error:invalid-data]]'));
 	}
+	toggleFollow('unfollow', socket.uid, data.uid, callback);
 };
 
 function toggleFollow(method, uid, theiruid, callback) {
@@ -194,7 +201,7 @@ SocketUser.saveSettings = function(socket, data, callback) {
 				return next(null, true);
 			}
 			user.isAdminOrGlobalMod(socket.uid, next);
-		}, 
+		},
 		function(allowed, next) {
 			if (!allowed) {
 				return next(new Error('[[error:no-privileges]]'));
@@ -205,19 +212,17 @@ SocketUser.saveSettings = function(socket, data, callback) {
 };
 
 SocketUser.setTopicSort = function(socket, sort, callback) {
-	if (socket.uid) {
-		user.setSetting(socket.uid, 'topicPostSort', sort, callback);
+	if (!socket.uid) {
+		return callback();
 	}
+	user.setSetting(socket.uid, 'topicPostSort', sort, callback);
 };
 
 SocketUser.setCategorySort = function(socket, sort, callback) {
-	if (socket.uid) {
-		user.setSetting(socket.uid, 'categoryTopicSort', sort, callback);
+	if (!socket.uid) {
+		return callback();
 	}
-};
-
-SocketUser.getOnlineAnonCount = function(socket, data, callback) {
-	callback(null, module.parent.exports.getOnlineAnonCount());
+	user.setSetting(socket.uid, 'categoryTopicSort', sort, callback);
 };
 
 SocketUser.getUnreadCount = function(socket, data, callback) {
@@ -332,5 +337,17 @@ SocketUser.invite = function(socket, email, callback) {
 
 };
 
+SocketUser.getUserByUID = function(socket, uid, callback) {
+	apiController.getUserDataByUID(socket.uid, uid, callback);
+};
+
+SocketUser.getUserByUsername = function(socket, username, callback) {
+	apiController.getUserDataByUsername(socket.uid, username, callback);
+};
+
+SocketUser.getUserByEmail = function(socket, email, callback) {
+	apiController.getUserDataByEmail(socket.uid, email, callback);
+};
+
 
 module.exports = SocketUser;
diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js
index 6149eff50d..cf886b1d48 100644
--- a/src/socket.io/user/picture.js
+++ b/src/socket.io/user/picture.js
@@ -2,6 +2,7 @@
 
 var async = require('async');
 var winston = require('winston');
+var path = require('path');
 
 var user = require('../../user');
 var plugins = require('../../plugins');
@@ -50,7 +51,7 @@ module.exports = function(SocketUser) {
 
 	SocketUser.uploadProfileImageFromUrl = function(socket, data, callback) {
 		if (!socket.uid || !data.url || !data.uid) {
-			return;
+			return callback(new Error('[[error:invalid-data]]'));
 		}
 
 		user.isAdminOrSelf(socket.uid, data.uid, function(err) {
@@ -65,7 +66,7 @@ module.exports = function(SocketUser) {
 
 	SocketUser.removeUploadedPicture = function(socket, data, callback) {
 		if (!socket.uid || !data.uid) {
-			return;
+			return callback(new Error('[[error:invalid-data]]'));
 		}
 
 		async.waterfall([
@@ -73,20 +74,21 @@ module.exports = function(SocketUser) {
 				user.isAdminOrSelf(socket.uid, data.uid, next);
 			},
 			function (next) {
-				user.getUserField(data.uid, 'uploadedpicture', next);
+				user.getUserFields(data.uid, ['uploadedpicture', 'picture'], next);
 			},
-			function(uploadedPicture, next) {
-				if (!uploadedPicture.startsWith('http')) {
-					require('fs').unlink(uploadedPicture, function(err) {
+			function(userData, next) {
+				if (!userData.uploadedpicture.startsWith('http')) {
+					require('fs').unlink(path.join(__dirname, '../../../public', userData.uploadedpicture), function(err) {
 						if (err) {
 							winston.error(err);
 						}
 					});
 				}
-				user.setUserField(data.uid, 'uploadedpicture', '', next);
-			},
-			function(next) {
-				user.getUserField(data.uid, 'picture', next);
+
+				user.setUserFields(data.uid, {
+					uploadedpicture: '',
+					picture: userData.uploadedpicture === userData.picture ? '' : userData.picture	// if current picture is uploaded picture, reset to user icon
+				}, next);
 			}
 		], callback);
 	};
diff --git a/src/topics.js b/src/topics.js
index 4f7f5a066d..385d3ae558 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -1,15 +1,16 @@
 "use strict";
 
-var async = require('async'),
-	_ = require('underscore'),
-
-	db = require('./database'),
-	posts = require('./posts'),
-	utils = require('../public/src/utils'),
-	plugins = require('./plugins'),
-	user = require('./user'),
-	categories = require('./categories'),
-	privileges = require('./privileges');
+var async = require('async');
+var _ = require('underscore');
+
+var db = require('./database');
+var posts = require('./posts');
+var utils = require('../public/src/utils');
+var plugins = require('./plugins');
+var user = require('./user');
+var categories = require('./categories');
+var privileges = require('./privileges');
+var social = require('./social');
 
 (function(Topics) {
 
@@ -125,11 +126,14 @@ var async = require('async'),
 						user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status'], next);
 					},
 					categories: function(next) {
-						categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next);
+						categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'image', 'bgColor', 'color', 'disabled'], next);
 					},
 					hasRead: function(next) {
 						Topics.hasReadTopics(tids, uid, next);
 					},
+					bookmarks: function(next) {
+						Topics.getUserBookmarks(tids, uid, next);
+					},
 					teasers: function(next) {
 						Topics.getTeasers(topics, next);
 					},
@@ -154,6 +158,7 @@ var async = require('async'),
 						topics[i].locked = parseInt(topics[i].locked, 10) === 1;
 						topics[i].deleted = parseInt(topics[i].deleted, 10) === 1;
 						topics[i].unread = !results.hasRead[i];
+						topics[i].bookmark = results.bookmarks[i];
 						topics[i].unreplied = !topics[i].teaser;
 					}
 				}
@@ -179,7 +184,8 @@ var async = require('async'),
 					threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
 					tags: async.apply(Topics.getTopicTagsObjects, topicData.tid),
 					isFollowing: async.apply(Topics.isFollowing, [topicData.tid], uid),
-					bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid)
+					bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid),
+					postSharing: async.apply(social.getActivePostSharing)
 				}, next);
 			},
 			function (results, next) {
@@ -189,6 +195,7 @@ var async = require('async'),
 				topicData.tags = results.tags;
 				topicData.isFollowing = results.isFollowing[0];
 				topicData.bookmark = results.bookmark;
+				topicData.postSharing = results.postSharing;
 
 				topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
 				topicData.deleted = parseInt(topicData.deleted, 10) === 1;
@@ -289,6 +296,17 @@ var async = require('async'),
 		db.sortedSetScore('tid:' + tid + ':bookmarks', uid, callback);
 	};
 
+	Topics.getUserBookmarks = function(tids, uid, callback) {
+		if (!parseInt(uid, 10)) {
+			return callback(null, tids.map(function() {
+				return null;
+			}));
+		}
+		db.sortedSetsScore(tids.map(function(tid) {
+			return 'tid:' + tid + ':bookmarks';
+		}), uid, callback);
+	};
+
 	Topics.setUserBookmark = function(tid, uid, index, callback) {
 		db.sortedSetAdd('tid:' + tid + ':bookmarks', index, uid, callback);
 	};
diff --git a/src/topics/create.js b/src/topics/create.js
index 6c7c4bccf1..dea303709c 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -1,17 +1,17 @@
 
 'use strict';
 
-var async = require('async'),
-	validator = require('validator'),
-	db = require('../database'),
-	utils = require('../../public/src/utils'),
-	plugins = require('../plugins'),
-	analytics = require('../analytics'),
-	user = require('../user'),
-	meta = require('../meta'),
-	posts = require('../posts'),
-	privileges = require('../privileges'),
-	categories = require('../categories');
+var async = require('async');
+var validator = require('validator');
+var db = require('../database');
+var utils = require('../../public/src/utils');
+var plugins = require('../plugins');
+var analytics = require('../analytics');
+var user = require('../user');
+var meta = require('../meta');
+var posts = require('../posts');
+var privileges = require('../privileges');
+var categories = require('../categories');
 
 module.exports = function(Topics) {
 
@@ -25,21 +25,13 @@ module.exports = function(Topics) {
 				db.incrObjectField('global', 'nextTid', next);
 			},
 			function(tid, next) {
-				var slug = utils.slugify(data.title);
-
-				if (!slug.length) {
-					return callback(new Error('[[error:invalid-title]]'));
-				}
-
-				slug = tid + '/' + slug;
-
 				topicData = {
 					'tid': tid,
 					'uid': data.uid,
 					'cid': data.cid,
 					'mainPid': 0,
 					'title': data.title,
-					'slug': slug,
+					'slug': tid + '/' + (utils.slugify(data.title) || 'topic'),
 					'timestamp': timestamp,
 					'lastposttime': 0,
 					'postcount': 0,
@@ -307,7 +299,7 @@ module.exports = function(Topics) {
 				postData.display_moderator_tools = true;
 				postData.display_move_tools = true;
 				postData.selfPost = false;
-				postData.relativeTime = utils.toISOString(postData.timestamp);
+				postData.timestampISO = utils.toISOString(postData.timestamp);
 				postData.topic.title = validator.escape(postData.topic.title);
 
 				next(null, postData);
diff --git a/src/topics/data.js b/src/topics/data.js
index 48aa81419e..9d153b5b78 100644
--- a/src/topics/data.js
+++ b/src/topics/data.js
@@ -59,8 +59,8 @@ module.exports = function(Topics) {
 			return;
 		}
 		topic.titleRaw = topic.title;
-		topic.title = validator.escape(topic.title);
-		topic.relativeTime = utils.toISOString(topic.timestamp);
+		topic.title = validator.escape(String(topic.title));
+		topic.timestampISO = utils.toISOString(topic.timestamp);
 		topic.lastposttimeISO = utils.toISOString(topic.lastposttime);
 	}
 
@@ -78,4 +78,8 @@ module.exports = function(Topics) {
 		db.setObjectField('topic:' + tid, field, value, callback);
 	};
 
+	Topics.deleteTopicField = function(tid, field, callback) {
+		db.deleteObjectField('topic:' + tid, field, callback);
+	};
+
 };
\ No newline at end of file
diff --git a/src/topics/delete.js b/src/topics/delete.js
index 8f2794fa17..97d617d878 100644
--- a/src/topics/delete.js
+++ b/src/topics/delete.js
@@ -11,7 +11,7 @@ var async = require('async'),
 
 module.exports = function(Topics) {
 
-	Topics.delete = function(tid, callback) {
+	Topics.delete = function(tid, uid, callback) {
 		Topics.getTopicFields(tid, ['cid'], function(err, topicData) {
 			if (err) {
 				return callback(err);
@@ -38,7 +38,7 @@ module.exports = function(Topics) {
 		});
 	};
 
-	Topics.restore = function(tid, callback) {
+	Topics.restore = function(tid, uid, callback) {
 		Topics.getTopicFields(tid, ['cid', 'lastposttime', 'postcount', 'viewcount'], function(err, topicData) {
 			if (err) {
 				return callback(err);
@@ -103,12 +103,12 @@ module.exports = function(Topics) {
 				posts.purge(mainPid, uid, next);
 			},
 			function (next) {
-				Topics.purge(tid, next);
+				Topics.purge(tid, uid, next);
 			}
 		], callback);
 	};
 
-	Topics.purge = function(tid, callback) {
+	Topics.purge = function(tid, uid, callback) {
 		async.parallel([
 			function(next) {
 				db.deleteAll([
diff --git a/src/topics/follow.js b/src/topics/follow.js
index dc441b1ca4..c05a796741 100644
--- a/src/topics/follow.js
+++ b/src/topics/follow.js
@@ -100,7 +100,9 @@ module.exports = function(Topics) {
 
 	Topics.notifyFollowers = function(postData, exceptUid, callback) {
 		callback = callback || function() {};
-		var followers, title;
+		var followers;
+		var title;
+		var titleEscaped;
 
 		async.waterfall([
 			function (next) {
@@ -126,12 +128,14 @@ module.exports = function(Topics) {
 					return callback();
 				}
 				title = postData.topic.title;
+
 				if (title) {
 					title = S(title).decodeHTMLEntities().s;
+					titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
 				}
 
 				notifications.create({
-					bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + title + ']]',
+					bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
 					bodyLong: postData.content,
 					pid: postData.pid,
 					nid: 'new_post:tid:' + postData.topic.tid + ':pid:' + postData.pid + ':uid:' + exceptUid,
@@ -162,7 +166,7 @@ module.exports = function(Topics) {
 							emailer.send('notif_post', toUid, {
 								pid: postData.pid,
 								subject: '[' + (meta.config.title || 'NodeBB') + '] ' + title,
-								intro: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + title + ']]',
+								intro: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
 								postBody: postData.content.replace(/"\/\//g, '"http://'),
 								site_title: meta.config.title || 'NodeBB',
 								username: data.userData.username,
diff --git a/src/topics/fork.js b/src/topics/fork.js
index fa4633fdd6..948cb3207e 100644
--- a/src/topics/fork.js
+++ b/src/topics/fork.js
@@ -1,14 +1,14 @@
 
 'use strict';
 
-var async = require('async'),
-	winston = require('winston'),
-
-	db = require('../database'),
-	user = require('../user'),
-	posts = require('../posts'),
-	privileges = require('../privileges'),
-	plugins = require('../plugins');
+var async = require('async');
+var winston = require('winston');
+var db = require('../database');
+var user = require('../user');
+var posts = require('../posts');
+var privileges = require('../privileges');
+var plugins = require('../plugins');
+var meta = require('../meta');
 
 
 module.exports = function(Topics) {
@@ -18,8 +18,10 @@ module.exports = function(Topics) {
 			title = title.trim();
 		}
 
-		if (!title) {
-			return callback(new Error('[[error:invalid-title]]'));
+		if (title.length < parseInt(meta.config.minimumTitleLength, 10)) {
+			return callback(new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]'));
+		} else if (title.length > parseInt(meta.config.maximumTitleLength, 10)) {
+			return callback(new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]'));
 		}
 
 		if (!pids || !pids.length) {
diff --git a/src/topics/posts.js b/src/topics/posts.js
index 85f2979513..445eab4637 100644
--- a/src/topics/posts.js
+++ b/src/topics/posts.js
@@ -1,16 +1,15 @@
 
-
 'use strict';
 
-var async = require('async'),
-	_ = require('underscore'),
-	validator = require('validator'),
+var async = require('async');
+var _ = require('underscore');
+var validator = require('validator');
 
-	db = require('../database'),
-	user = require('../user'),
-	favourites = require('../favourites'),
-	posts = require('../posts'),
-	meta = require('../meta');
+var db = require('../database');
+var user = require('../user');
+var favourites = require('../favourites');
+var posts = require('../posts');
+var meta = require('../meta');
 
 module.exports = function(Topics) {
 
@@ -138,14 +137,20 @@ module.exports = function(Topics) {
 		});
 	};
 
-	Topics.modifyPostsByPrivilege = function(postData, topicPrivileges) {
-		postData.forEach(function(post) {
+	Topics.modifyPostsByPrivilege = function(topicData, topicPrivileges) {
+		var loggedIn = !!parseInt(topicPrivileges.uid, 10);
+		topicData.posts.forEach(function(post) {
 			if (post) {
 				post.display_moderator_tools = topicPrivileges.isAdminOrMod || post.selfPost;
 				post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0;
-				post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || !post.deleted;
+				post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || ((loggedIn || topicData.postSharing.length) && !post.deleted);
+				post.ip = topicPrivileges.isAdminOrMod ? post.ip : undefined;
+
 				if (post.deleted && !(topicPrivileges.isAdminOrMod || post.selfPost)) {
 					post.content = '[[topic:post_is_deleted]]';
+					if (post.user) {
+						post.user.signature = '';
+					}
 				}
 			}
 		});
diff --git a/src/topics/tags.js b/src/topics/tags.js
index 1b47a5d0e6..83702331b7 100644
--- a/src/topics/tags.js
+++ b/src/topics/tags.js
@@ -1,12 +1,13 @@
 
 'use strict';
 
-var async = require('async'),
+var async = require('async');
 
-	db = require('../database'),
-	meta = require('../meta'),
-	_ = require('underscore'),
-	plugins = require('../plugins');
+var db = require('../database');
+var meta = require('../meta');
+var _ = require('underscore');
+var plugins = require('../plugins');
+var utils = require('../../public/src/utils');
 
 
 module.exports = function(Topics) {
@@ -24,7 +25,9 @@ module.exports = function(Topics) {
 			},
 			function (data, next) {
 				tags = data.tags.slice(0, meta.config.maximumTagsPerTopic || 5);
-				tags = tags.map(Topics.cleanUpTag).filter(function(tag, index, array) {
+				tags = tags.map(function(tag) {
+					return utils.cleanUpTag(tag, meta.config.maximumTagLength);
+				}).filter(function(tag, index, array) {
 					return tag && tag.length >= (meta.config.minimumTagLength || 3) && array.indexOf(tag) === index;
 				});
 
@@ -45,20 +48,6 @@ module.exports = function(Topics) {
 		], callback);
 	};
 
-	Topics.cleanUpTag = function(tag) {
-		if (typeof tag !== 'string' || !tag.length ) {
-			return '';
-		}
-		tag = tag.trim().toLowerCase();
-		tag = tag.replace(/[,\/#!$%\^\*;:{}=_`<>'"~()?\|]/g, '');
-		tag = tag.substr(0, meta.config.maximumTagLength || 15).trim();
-		var matches = tag.match(/^[.-]*(.+?)[.-]*$/);
-		if (matches && matches.length > 1) {
-			tag = matches[1];
-		}
-		return tag;
-	};
-
 	Topics.updateTag = function(tag, data, callback) {
 		db.setObject('tag:' + tag, data, callback);
 	};
diff --git a/src/topics/teaser.js b/src/topics/teaser.js
index 3973e8ebd6..5d730de74d 100644
--- a/src/topics/teaser.js
+++ b/src/topics/teaser.js
@@ -60,7 +60,7 @@ module.exports = function(Topics) {
 					}
 
 					post.user = users[post.uid];
-					post.timestamp = utils.toISOString(post.timestamp);
+					post.timestampISO = utils.toISOString(post.timestamp);
 					tidToPost[post.tid] = post;
 					posts.parsePost(post, next);
 				}, next);
@@ -74,7 +74,7 @@ module.exports = function(Topics) {
 						tidToPost[topic.tid].index = meta.config.teaserPost === 'first' ? 1 : counts[index];
 						if (tidToPost[topic.tid].content) {
 							var s = S(tidToPost[topic.tid].content);
-							tidToPost[topic.tid].content = s.stripTags.apply(s, utils.stripTags).s;
+							tidToPost[topic.tid].content = s.stripTags.apply(s, utils.stripTags.concat(['img'])).s;
 						}
 					}
 					return tidToPost[topic.tid];
diff --git a/src/topics/tools.js b/src/topics/tools.js
index 3deea91b46..1cb02ae514 100644
--- a/src/topics/tools.js
+++ b/src/topics/tools.js
@@ -49,7 +49,7 @@ module.exports = function(Topics) {
 					return callback(new Error('[[error:topic-already-restored]]'));
 				}
 
-				Topics[isDelete ? 'delete' : 'restore'](tid, next);
+				Topics[isDelete ? 'delete' : 'restore'](tid, uid, next);
 			},
 			function (next) {
 				topicData.deleted = isDelete ? 1 : 0;
diff --git a/src/upgrade.js b/src/upgrade.js
index 2d2001fc41..f6f1ce0093 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -384,7 +384,7 @@ Upgrade.upgrade = function(callback) {
 					},
 					function (exists, next) {
 						if (exists) {
-							return next();
+							return next(null, null);
 						}
 						groups.create({
 							name: 'Global Moderators',
diff --git a/src/user.js b/src/user.js
index e9546b8c6e..19c60fc1fd 100644
--- a/src/user.js
+++ b/src/user.js
@@ -39,7 +39,6 @@ var	async = require('async'),
 			if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) {
 				return callback(err);
 			}
-
 			User.setUserField(uid, 'lastonline', now, callback);
 		});
 	};
@@ -70,7 +69,7 @@ var	async = require('async'),
 		if (set === 'users:online') {
 			var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
 			var now = Date.now();
-			db.getSortedSetRevRangeByScore(set, start, count, now, now - 300000, callback);
+			db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - 300000, callback);
 		} else {
 			db.getSortedSetRevRange(set, start, stop, callback);
 		}
@@ -257,4 +256,3 @@ var	async = require('async'),
 
 
 }(exports));
-
diff --git a/src/user/approval.js b/src/user/approval.js
index a4a3981002..94e0f097e5 100644
--- a/src/user/approval.js
+++ b/src/user/approval.js
@@ -47,7 +47,8 @@ module.exports = function(User) {
 		notifications.create({
 			bodyShort: '[[notifications:new_register, ' + username + ']]',
 			nid: 'new_register:' + username,
-			path: '/admin/manage/registration'
+			path: '/admin/manage/registration',
+			mergeId: 'new_register'
 		}, function(err, notification) {
 			if (err || !notification) {
 				return callback(err);
@@ -146,11 +147,16 @@ module.exports = function(User) {
 				db.getObjects(keys, next);
 			},
 			function(users, next) {
-				users.forEach(function(user, index) {
-					if (user) {
-						user.timestamp = utils.toISOString(data[index].score);
+				users = users.map(function(user, index) {
+					if (!user) {
+						return null;
 					}
-				});
+
+					user.timestampISO = utils.toISOString(data[index].score);
+					delete user.hashedPassword;
+
+					return user;
+				}).filter(Boolean);
 
 				async.map(users, function(user, next) {
 					if (!user) {
@@ -160,18 +166,25 @@ module.exports = function(User) {
 					// temporary: see http://www.stopforumspam.com/forum/viewtopic.php?id=6392
 					user.ip = user.ip.replace('::ffff:', '');
 
-					request('http://api.stopforumspam.org/api?ip=' + user.ip + '&email=' + user.email + '&username=' + user.username + '&f=json', function (err, response, body) {
+					request({
+						method: 'get',
+						url: 'http://api.stopforumspam.org/api' +
+								'?ip=' + encodeURIComponent(user.ip) +
+								'&email=' + encodeURIComponent(user.email) +
+								'&username=' + encodeURIComponent(user.username) +
+								'&f=json',
+						json: true
+					}, function (err, response, body) {
 						if (err) {
 							return next(null, user);
 						}
 						if (response.statusCode === 200) {
-							var data = JSON.parse(body);
-							user.spamData = data;
-
-							user.usernameSpam = data.username.frequency > 0 || data.username.appears > 0;
-							user.emailSpam = data.email.frequency > 0 || data.email.appears > 0;
-							user.ipSpam = data.ip.frequency > 0 || data.ip.appears > 0;
+							user.spamData = body;
+							user.usernameSpam = body.username.frequency > 0 || body.username.appears > 0;
+							user.emailSpam = body.email.frequency > 0 || body.email.appears > 0;
+							user.ipSpam = body.ip.frequency > 0 || body.ip.appears > 0;
 						}
+
 						next(null, user);
 					});
 				}, next);
diff --git a/src/user/create.js b/src/user/create.js
index 1d1543e863..ae69f1ad5d 100644
--- a/src/user/create.js
+++ b/src/user/create.js
@@ -1,14 +1,13 @@
 'use strict';
 
-var async = require('async'),
-	db = require('../database'),
-	utils = require('../../public/src/utils'),
-	validator = require('validator'),
-	plugins = require('../plugins'),
-	groups = require('../groups'),
-	meta = require('../meta'),
-	notifications = require('../notifications'),
-	translator = require('../../public/src/modules/translator');
+var async = require('async');
+var db = require('../database');
+var utils = require('../../public/src/utils');
+var validator = require('validator');
+var plugins = require('../plugins');
+var groups = require('../groups');
+var meta = require('../meta');
+
 
 module.exports = function(User) {
 
@@ -90,7 +89,11 @@ module.exports = function(User) {
 								db.sortedSetAdd('userslug:uid', userData.uid, userData.userslug, next);
 							},
 							function(next) {
-								db.sortedSetsAdd(['users:joindate', 'users:online', 'users:notvalidated'], timestamp, userData.uid, next);
+								var sets = ['users:joindate', 'users:online'];
+								if (parseInt(userData.uid) !== 1) {
+									sets.push('users:notvalidated');
+								}
+								db.sortedSetsAdd(sets, timestamp, userData.uid, next);
 							},
 							function(next) {
 								db.sortedSetsAdd(['users:postcount', 'users:reputation'], 0, userData.uid, next);
@@ -176,7 +179,7 @@ module.exports = function(User) {
 					next();
 				}
 			}
-		}, function(err, results) {
+		}, function(err) {
 			callback(err);
 		});
 	};
diff --git a/src/user/data.js b/src/user/data.js
index 04c6d0e999..66fedbefcd 100644
--- a/src/user/data.js
+++ b/src/user/data.js
@@ -94,7 +94,7 @@ module.exports = function(User) {
 				return;
 			}
 
-			user.username = validator.escape(user.username || '');
+			user.username = validator.escape(user.username ? user.username.toString() : '');
 
 			if (user.password) {
 				user.password = undefined;
diff --git a/src/user/delete.js b/src/user/delete.js
index 921bb62d32..3ab5176725 100644
--- a/src/user/delete.js
+++ b/src/user/delete.js
@@ -11,16 +11,16 @@ var async = require('async'),
 
 module.exports = function(User) {
 
-	User.delete = function(uid, callback) {
+	User.delete = function(callerUid, uid, callback) {
 		if (!parseInt(uid, 10)) {
 			return callback(new Error('[[error:invalid-uid]]'));
 		}
 		async.waterfall([
 			function(next) {
-				deletePosts(uid, next);
+				deletePosts(callerUid, uid, next);
 			},
 			function(next) {
-				deleteTopics(uid, next);
+				deleteTopics(callerUid, uid, next);
 			},
 			function(next) {
 				User.deleteAccount(uid, next);
@@ -28,17 +28,19 @@ module.exports = function(User) {
 		], callback);
 	};
 
-	function deletePosts(uid, callback) {
-		deleteSortedSetElements('uid:' + uid + ':posts', posts.purge, callback);
-	}
-
-	function deleteTopics(uid, callback) {
-		deleteSortedSetElements('uid:' + uid + ':topics', topics.purge, callback);
+	function deletePosts(callerUid, uid, callback) {
+		batch.processSortedSet('uid:' + uid + ':posts', function(ids, next) {
+			async.eachSeries(ids, function(pid, netx) {
+				posts.purge(pid, callerUid, next);
+			}, next);
+		}, {alwaysStartAt: 0}, callback);
 	}
 
-	function deleteSortedSetElements(set, deleteMethod, callback) {
-		batch.processSortedSet(set, function(ids, next) {
-			async.eachLimit(ids, 10, deleteMethod, next);
+	function deleteTopics(callerUid, uid, callback) {
+		batch.processSortedSet('uid:' + uid + ':topics', function(ids, next) {
+			async.eachSeries(ids, function(tid, next) {
+				topics.purge(tid, callerUid, next);
+			}, next);
 		}, {alwaysStartAt: 0}, callback);
 	}
 
@@ -145,7 +147,7 @@ module.exports = function(User) {
 					return pid && array.indexOf(pid) === index;
 				});
 
-				async.eachLimit(pids, 50, function(pid, next) {
+				async.eachSeries(pids, function(pid, next) {
 					favourites.unvote(pid, uid, next);
 				}, next);
 			}
diff --git a/src/user/digest.js b/src/user/digest.js
index cc2c768b71..81b6ea0220 100644
--- a/src/user/digest.js
+++ b/src/user/digest.js
@@ -1,17 +1,16 @@
 "use strict";
 
-var	async = require('async'),
-	winston = require('winston'),
-	nconf = require('nconf'),
-
-	db = require('../database'),
-	meta = require('../meta'),
-	user = require('../user'),
-	topics = require('../topics'),
-	batch = require('../batch'),
-	plugins = require('../plugins'),
-	emailer = require('../emailer'),
-	utils = require('../../public/src/utils');
+var	async = require('async');
+var winston = require('winston');
+var nconf = require('nconf');
+
+var db = require('../database');
+var meta = require('../meta');
+var user = require('../user');
+var topics = require('../topics');
+var plugins = require('../plugins');
+var emailer = require('../emailer');
+var utils = require('../../public/src/utils');
 
 (function(Digest) {
 	Digest.execute = function(interval) {
@@ -100,7 +99,7 @@ var	async = require('async'),
 					}
 
 					emailer.send('digest', userObj.uid, {
-						subject: '[' + meta.config.title + '] Digest for ' + now.getFullYear()+ '/' + (now.getMonth()+1) + '/' + now.getDate(),
+						subject: '[' + meta.config.title + '] [[email:digest.subject, ' + (now.getFullYear()+ '/' + (now.getMonth()+1) + '/' + now.getDate()) + ']]',
 						username: userObj.username,
 						userslug: userObj.userslug,
 						url: nconf.get('url'),
diff --git a/src/user/invite.js b/src/user/invite.js
index 638432e810..34521550a5 100644
--- a/src/user/invite.js
+++ b/src/user/invite.js
@@ -120,7 +120,31 @@ module.exports = function(User) {
 		], callback);
 	};
 
-	User.deleteInvitation = function(email, callback) {
+	User.deleteInvitation = function(invitedBy, email, callback) {
+		callback = callback || function() {};
+		async.waterfall([
+			function getInvitedByUid(next) {
+				User.getUidByUsername(invitedBy, next);
+			},
+			function deleteRegistries(invitedByUid, next) {
+				if (!invitedByUid) {
+					return next(new Error('[[error:invalid-username]]'));
+				}
+				async.parallel([
+					function deleteFromReferenceList(next) {
+						db.setRemove('invitation:uid:' + invitedByUid, email, next);
+					},
+					function deleteInviteKey(next) {
+						db.delete('invitation:email:' + email, callback);
+					}
+				], function(err) {
+					next(err)
+				});
+			}
+		], callback);
+	};
+
+	User.deleteInvitationKey = function(email, callback) {
 		callback = callback || function() {};
 		db.delete('invitation:email:' + email, callback);
 	};
diff --git a/src/user/notifications.js b/src/user/notifications.js
index 6c7634362a..9ad100db2b 100644
--- a/src/user/notifications.js
+++ b/src/user/notifications.js
@@ -188,10 +188,9 @@ var async = require('async'),
 	}
 
 	UserNotifications.getDailyUnread = function(uid, callback) {
-		var	now = Date.now(),
-			yesterday = now - (1000*60*60*24);	// Approximate, can be more or less depending on time changes, makes no difference really.
+		var yesterday = Date.now() - (1000 * 60 * 60 * 24);	// Approximate, can be more or less depending on time changes, makes no difference really.
 
-		db.getSortedSetRevRangeByScore('uid:' + uid + ':notifications:unread', 0, 20, now, yesterday, function(err, nids) {
+		db.getSortedSetRevRangeByScore('uid:' + uid + ':notifications:unread', 0, 20, '+inf', yesterday, function(err, nids) {
 			if (err) {
 				return callback(err);
 			}
@@ -208,8 +207,30 @@ var async = require('async'),
 		if (!parseInt(uid, 10)) {
 			return callback(null, 0);
 		}
-		db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, function(err, nids) {
-			callback(err, Array.isArray(nids) ? nids.length : 0);
+
+		// Collapse any notifications with identical mergeIds
+		async.waterfall([
+			async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':notifications:unread', 0, 99),
+			function(nids, next) {
+				var keys = nids.map(function(nid) {
+					return 'notifications:' + nid;
+				});
+
+				db.getObjectsFields(keys, ['mergeId'], next);
+			}
+		], function(err, mergeIds) {
+			// A missing (null) mergeId means that notification is counted separately.
+			mergeIds = mergeIds.map(function(set) {
+				return set.mergeId;
+			});
+
+			callback(err, mergeIds.reduce(function(count, cur, idx, arr) {
+				if (cur === null || idx === arr.indexOf(cur)) {
+					++count;
+				}
+
+				return count;
+			}, 0));
 		});
 	};
 
diff --git a/src/user/picture.js b/src/user/picture.js
index db0ea234f1..c9a760f10e 100644
--- a/src/user/picture.js
+++ b/src/user/picture.js
@@ -37,39 +37,39 @@ module.exports = function(User) {
 			function(next) {
 				next(!extension ? new Error('[[error:invalid-image-extension]]') : null);
 			},
-			function(next) {
-				file.isFileTypeAllowed(picture.path, next);
-			},
-			function(path, next) {
-				image.resizeImage({
-					path: picture.path,
-					extension: extension,
-					width: imageDimension,
-					height: imageDimension
-				}, next);
-			},
-			function(next) {
-				if (convertToPNG) {
-					image.normalise(picture.path, extension, next);
-				} else {
-					next();
-				}
-			},
 			function(next) {
 				if (plugins.hasListeners('filter:uploadImage')) {
 					return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, next);
 				}
 
 				var filename = updateUid + '-profileimg' + (convertToPNG ? '.png' : extension);
-				
+
 				async.waterfall([
+					function(next) {
+						file.isFileTypeAllowed(picture.path, next);
+					},
+					function(next) {
+						image.resizeImage({
+							path: picture.path,
+							extension: extension,
+							width: imageDimension,
+							height: imageDimension
+						}, next);
+					},
+					function(next) {
+						if (convertToPNG) {
+							image.normalise(picture.path, extension, next);
+						} else {
+							next();
+						}
+					},
 					function(next) {
 						User.getUserField(updateUid, 'uploadedpicture', next);
 					},
 					function(oldpicture, next) {
 						if (!oldpicture) {
 							return file.saveFileToLocal(filename, 'profile', picture.path, next);
-						}		
+						}
 						var oldpicturePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), 'profile', path.basename(oldpicture));
 
 						fs.unlink(oldpicturePath, function (err) {
@@ -79,7 +79,7 @@ module.exports = function(User) {
 
 							file.saveFileToLocal(filename, 'profile', picture.path, next);
 						});
-					},					
+					},
 				], next);
 			},
 			function(_image, next) {
@@ -167,12 +167,9 @@ module.exports = function(User) {
 				}, next);
 			},
 			function(next) {
-				file.isFileTypeAllowed(data.file.path, next);
-			},
-			function(tempPath, next) {
 				var image = {
 					name: 'profileCover',
-					path: tempPath,
+					path: data.file.path,
 					uid: data.uid
 				};
 
@@ -181,16 +178,20 @@ module.exports = function(User) {
 				}
 
 				var filename = data.uid + '-profilecover';
-				file.saveFileToLocal(filename, 'profile', image.path, function(err, upload) {
-					if (err) {
-						return next(err);
+				async.waterfall([
+					function (next) {
+						file.isFileTypeAllowed(data.file.path, next);
+					},
+					function (next) {
+						file.saveFileToLocal(filename, 'profile', image.path, next);
+					},
+					function (upload, next) {
+						next(null, {
+							url: nconf.get('relative_path') + upload.url,
+							name: image.name
+						});
 					}
-
-					next(null, {
-						url: nconf.get('relative_path') + upload.url,
-						name: image.name
-					});
-				});
+				], next);
 			},
 			function(uploadData, next) {
 				url = uploadData.url;
diff --git a/src/user/posts.js b/src/user/posts.js
index e9f64ef073..78a2db0923 100644
--- a/src/user/posts.js
+++ b/src/user/posts.js
@@ -72,6 +72,9 @@ module.exports = function(User) {
 			},
 			function(next) {
 				User.setUserField(postData.uid, 'lastposttime', postData.timestamp, next);
+			},
+			function(next) {
+				User.updateLastOnlineTime(postData.uid, next);
 			}
 		], callback);
 	};
diff --git a/src/user/reset.js b/src/user/reset.js
index 26acc5ecbc..222e988de6 100644
--- a/src/user/reset.js
+++ b/src/user/reset.js
@@ -42,6 +42,20 @@ var async = require('async'),
 		});
 	};
 
+	function canGenerate(uid, callback) {
+		async.waterfall([
+			function (next) {
+				db.sortedSetScore('reset:issueDate:uid', uid, next);
+			},
+			function (score, next) {
+				if (score > Date.now() - 1000 * 60) {
+					return next(new Error('[[error:cant-reset-password-more-than-once-a-minute]]'));
+				}
+				next();
+			}
+		], callback);
+	}
+
 	UserReset.send = function(email, callback) {
 		var uid;
 		async.waterfall([
@@ -54,6 +68,12 @@ var async = require('async'),
 				}
 
 				uid = _uid;
+				canGenerate(uid, next);
+			},
+			function(next) {
+				db.sortedSetAdd('reset:issueDate:uid', Date.now(), uid, next);
+			},
+			function(next) {
 				UserReset.generate(uid, next);
 			},
 			function(code, next) {
@@ -102,6 +122,7 @@ var async = require('async'),
 					async.apply(user.setUserField, uid, 'password', hash),
 					async.apply(db.deleteObjectField, 'reset:uid', code),
 					async.apply(db.sortedSetRemove, 'reset:issueDate', code),
+					async.apply(db.sortedSetRemove, 'reset:issueDate:uid', uid),
 					async.apply(user.reset.updateExpiry, uid),
 					async.apply(user.auth.resetLockout, uid)
 				], next);
@@ -110,9 +131,9 @@ var async = require('async'),
 	};
 
 	UserReset.updateExpiry = function(uid, callback) {
-		var oneDay = 1000*60*60*24,
-			expireDays = parseInt(meta.config.passwordExpiryDays || 0, 10),
-			expiry = Date.now() + (oneDay * expireDays);
+		var oneDay = 1000 * 60 * 60 * 24;
+		var expireDays = parseInt(meta.config.passwordExpiryDays || 0, 10);
+		var expiry = Date.now() + (oneDay * expireDays);
 
 		callback = callback || function() {};
 		user.setUserField(uid, 'passwordExpiry', expireDays > 0 ? expiry : 0, callback);
@@ -120,16 +141,26 @@ var async = require('async'),
 
 	UserReset.clean = function(callback) {
 		async.waterfall([
-			async.apply(db.getSortedSetRangeByScore, 'reset:issueDate', 0, -1, 0, Date.now() - twoHours),
-			function(tokens, next) {
-				if (!tokens.length) {
+			function(next) {
+				async.parallel({
+					tokens: function(next) {
+						db.getSortedSetRangeByScore('reset:issueDate', 0, -1, '-inf', Date.now() - twoHours, next);
+					},
+					uids: function(next) {
+						db.getSortedSetRangeByScore('reset:issueDate:uid', 0, -1, '-inf', Date.now() - twoHours, next);
+					}
+				}, next);
+			},
+			function(results, next) {
+				if (!results.tokens.length && !results.uids.length) {
 					return next();
 				}
 
-				winston.verbose('[UserReset.clean] Removing ' + tokens.length + ' reset tokens from database');
+				winston.verbose('[UserReset.clean] Removing ' + results.tokens.length + ' reset tokens from database');
 				async.parallel([
-					async.apply(db.deleteObjectFields, 'reset:uid', tokens),
-					async.apply(db.sortedSetRemove, 'reset:issueDate', tokens)
+					async.apply(db.deleteObjectFields, 'reset:uid', results.tokens),
+					async.apply(db.sortedSetRemove, 'reset:issueDate', results.tokens),
+					async.apply(db.sortedSetRemove, 'reset:issueDate:uid', results.uids)
 				], next);
 			}
 		], callback);
diff --git a/src/user/settings.js b/src/user/settings.js
index f1c4081b50..04e23e1152 100644
--- a/src/user/settings.js
+++ b/src/user/settings.js
@@ -76,6 +76,7 @@ module.exports = function(User) {
 			settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
 			settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
 			settings.bootswatchSkin = settings.bootswatchSkin || 'default';
+			settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
 
 			callback(null, settings);
 		});
@@ -120,7 +121,8 @@ module.exports = function(User) {
 			restrictChat: data.restrictChat,
 			topicSearchEnabled: data.topicSearchEnabled,
 			groupTitle: data.groupTitle,
-			homePageRoute: data.homePageCustom || data.homePageRoute
+			homePageRoute: data.homePageCustom || data.homePageRoute,
+			scrollToMyPost: data.scrollToMyPost
 		};
 
 		if (data.bootswatchSkin) {
diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl
index 2581c9c2ad..c4f9ddde1b 100644
--- a/src/views/admin/development/info.tpl
+++ b/src/views/admin/development/info.tpl
@@ -1,24 +1,48 @@
 <div class="info">
 	<div class="panel panel-default">
 		<div class="panel-heading">
-			<h3 class="panel-title">Info</h3>
+			<h3 class="panel-title">Info - You are on <strong>{host}:{port}</strong></h3>
 		</div>
 
 		<div class="panel-body">
-			<div class="highlight">
-				<pre>{info}</pre>
+			<table class="table table-striped">
+				<thead>
+					<tr>
+						<td>host</td>
+						<td>pid</td>
+						<td>nodejs</td>
+						<td>online</td>
+						<td>git</td>
+						<td>load</td>
+						<td>uptime</td>
+					</tr>
+				</thead>
+				<tbody>
+				<!-- BEGIN info -->
+				<tr>
+					<td>{info.os.hostname}:{info.process.port}</td>
+					<td>{info.process.pid}</td>
+					<td>{info.process.version}</td>
+					<td><span title="Registered">{info.stats.onlineRegisteredCount}</span> / <span title="Guest">{info.stats.onlineGuestCount}</span> / <span title="Sockets">{info.stats.socketCount}</span></td>
+					<td>{info.git.branch}@<a href="https://github.com/NodeBB/NodeBB/commit/{info.git.hash}" target="_blank">{info.git.hash}</a></td>
+					<td>{info.os.load}</td>
+					<td>{info.process.uptime}</td>
+				</tr>
+				<!-- END info -->
+				</tbody>
+			</table>
 			</div>
 		</div>
 	</div>
 
 	<div class="panel panel-default">
 		<div class="panel-heading">
-			<h3 class="panel-title">Stats</h3>
+			<h3 class="panel-title">Info</h3>
 		</div>
 
 		<div class="panel-body">
 			<div class="highlight">
-				<pre>{stats}</pre>
+				<pre>{infoJSON}</pre>
 			</div>
 		</div>
 	</div>
diff --git a/src/views/admin/footer.tpl b/src/views/admin/footer.tpl
index 02f80c73f0..de228ce0d7 100644
--- a/src/views/admin/footer.tpl
+++ b/src/views/admin/footer.tpl
@@ -2,40 +2,6 @@
 		</div>
 	</div>
 
-	<div id="upload-picture-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="Upload Picture" aria-hidden="true">
-		<div class="modal-dialog">
-			<div class="modal-content">
-				<div class="modal-header">
-					<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-					<h3 id="myModalLabel">Upload Picture</h3>
-				</div>
-				<div class="modal-body">
-					<form id="uploadForm" action="" method="post" enctype="multipart/form-data">
-						<div class="form-group">
-							<label for="userPhoto">Upload a picture</label>
-							<input type="file" id="userPhotoInput" name="files[]">
-							<p class="help-block"></p>
-						</div>
-						<input type="hidden" id="params" name="params">
-					</form>
-
-					<div id="upload-progress-box" class="progress progress-striped">
-						<div id="upload-progress-bar" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="0" aria-valuemin="0">
-							<span class="sr-only"> success</span>
-						</div>
-					</div>
-
-					<div id="alert-status" class="alert alert-info hide"></div>
-					<div id="alert-success" class="alert alert-success hide"></div>
-					<div id="alert-error" class="alert alert-danger hide"></div>
-				</div>
-				<div class="modal-footer">
-					<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
-					<button id="pictureUploadSubmitBtn" class="btn btn-primary">Upload Picture</button>
-				</div>
-			</div><!-- /.modal-content -->
-		</div><!-- /.modal-dialog -->
-	</div><!-- /.modal -->
 
 	<div class="alert-window alert-left-top"></div>
 	<div class="alert-window alert-left-bottom"></div>
diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl
index 2fdf04560c..c36f08a693 100644
--- a/src/views/admin/general/navigation.tpl
+++ b/src/views/admin/general/navigation.tpl
@@ -66,6 +66,12 @@
 							<span class="mdl-switch__label"><strong>Only display to Admins</strong></span>
 						</label>
 					</div>
+					<div class="checkbox">
+						<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
+							<input class="mdl-switch__input" type="checkbox" name="property:globalMod" <!-- IF enabled.properties.globalMod -->checked<!-- ENDIF enabled.properties.globalMod -->/>
+							<span class="mdl-switch__label"><strong>Only display to Global Moderators and Admins</strong></span>
+						</label>
+					</div>
 					<div class="checkbox">
 						<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 							<input class="mdl-switch__input" type="checkbox" name="property:loggedIn" <!-- IF enabled.properties.loggedIn -->checked<!-- ENDIF enabled.properties.loggedIn -->/> <span class="mdl-switch__label"><strong>Only display to logged in users</strong></span>
@@ -78,6 +84,15 @@
 						</label>
 					</div>
 
+					<strong>Installed Plugins Required:</strong>
+
+					<div class="checkbox">
+						<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
+							<input class="mdl-switch__input" type="checkbox" name="property:searchInstalled" <!-- IF enabled.properties.searchInstalled -->checked<!-- ENDIF enabled.properties.searchInstalled -->/>
+							<span class="mdl-switch__label"><strong>Search plugin</strong></span>
+						</label>
+					</div>
+
 					<button class="btn btn-danger delete">Delete</button>
 					<!-- IF enabled.enabled -->
 					<button class="btn btn-warning toggle">Disable</button>
diff --git a/src/views/admin/general/sounds.tpl b/src/views/admin/general/sounds.tpl
index 72036f72cc..dfdbae512d 100644
--- a/src/views/admin/general/sounds.tpl
+++ b/src/views/admin/general/sounds.tpl
@@ -63,7 +63,7 @@
 			<div class="panel-body">
 				<div class="input-group">
 					<span class="input-group-btn">
-						<input data-action="upload" data-target="logoUrl" data-route="{config.relative_path}/api/admin/upload/sound" type="button" class="btn btn-primary btn-block" value="Upload New Sound"></input>
+						<input data-action="upload" data-title="Upload Sound" data-route="{config.relative_path}/api/admin/upload/sound" type="button" class="btn btn-primary btn-block" value="Upload New Sound"></input>
 					</span>
 				</div>
 			</div>
diff --git a/src/views/admin/manage/category-analytics.tpl b/src/views/admin/manage/category-analytics.tpl
new file mode 100644
index 0000000000..eb02abd141
--- /dev/null
+++ b/src/views/admin/manage/category-analytics.tpl
@@ -0,0 +1,53 @@
+<a class="btn btn-primary" href="{config.relative_path}/admin/manage/categories"><i class="fa fa-fw fa-chevron-left"></i> Back to Categories List</a>
+
+<h3>Analytics for "{name}" category</h3>
+<hr />
+
+<div class="row">
+	<div class="col-sm-6 text-center">
+		<div class="panel panel-default">
+			<div class="panel-body">
+				<div><canvas id="pageviews:hourly" height="250"></canvas></div>
+				<p>
+					
+				</p>
+			</div>
+			<div class="panel-footer"><small><strong>Figure 1</strong> &ndash; Hourly page views for this category</small></div>
+		</div>
+	</div>
+	<div class="col-sm-6 text-center">
+		<div class="panel panel-default">
+			<div class="panel-body">
+				<div><canvas id="pageviews:daily" height="250"></canvas></div>
+				<p>
+					
+				</p>
+			</div>
+			<div class="panel-footer"><small><strong>Figure 2</strong> &ndash; Daily page views for this category</small></div>
+		</div>
+	</div>
+</div>
+<div class="row">
+	<div class="col-sm-6 text-center">
+		<div class="panel panel-default">
+			<div class="panel-body">
+				<div><canvas id="topics:daily" height="250"></canvas></div>
+				<p>
+					
+				</p>
+			</div>
+			<div class="panel-footer"><small><strong>Figure 3</strong> &ndash; Daily topics created in this category</small></div>
+		</div>
+	</div>
+	<div class="col-sm-6 text-center">
+		<div class="panel panel-default">
+			<div class="panel-body">
+				<div><canvas id="posts:daily" height="250"></canvas></div>
+				<p>
+					
+				</p>
+			</div>
+			<div class="panel-footer"><small><strong>Figure 4</strong> &ndash; Daily posts made in this category</small></div>
+		</div>
+	</div>
+</div>
\ No newline at end of file
diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl
index 1ca571010d..814b319913 100644
--- a/src/views/admin/manage/category.tpl
+++ b/src/views/admin/manage/category.tpl
@@ -3,7 +3,6 @@
 		<ul class="nav nav-pills">
 			<li class="active"><a href="#category-settings" data-toggle="tab">Category Settings</a></li>
 			<li><a href="#privileges" data-toggle="tab">Privileges</a></li>
-			<li><a href="#analytics" data-toggle="tab">Analytics</a></li>
 		</ul>
 		<br />
 		<div class="tab-content">
@@ -78,7 +77,7 @@
 							</div>
 							<div class="btn-group btn-group-justified">
 								<div class="btn-group">
-									<button type="button" data-cid="{category.cid}" data-name="image" data-value="{category.image}" class="btn btn-default upload-button"><i class="fa fa-upload"></i> Upload Image</button>
+									<button type="button" data-cid="{category.cid}" class="btn btn-default upload-button"><i class="fa fa-upload"></i> Upload Image</button>
 								</div>
 								<!-- IF category.image -->
 								<div class="btn-group">
@@ -87,6 +86,14 @@
 								<!-- ENDIF category.image -->
 							</div><br />
 
+							<fieldset>
+								<div class="form-group text-center">
+									<label for="category-image">Category Image</label>
+									<br/>
+									<input id="category-image" type="text" class="form-control" placeholder="Category Image" data-name="image" value="{category.image}" />
+								</div>
+							</fieldset>
+
 							<fieldset>
 								<div class="form-group text-center">
 									<label for="cid-{category.cid}-parentCid">Parent Category</label>
@@ -98,7 +105,8 @@
 									<button type="button" class="btn btn-default btn-block <!-- IF category.parent.name -->hide<!-- ENDIF category.parent.name -->" data-action="setParent"><i class="fa fa-sitemap"></i> (None)</button>
 								</div>
 							</fieldset>
-
+							<hr/>
+							<button class="btn btn-info btn-block copy-settings"><i class="fa fa-files-o"></i> Copy Settings From</button>
 							<hr />
 							<button class="btn btn-danger btn-block purge"><i class="fa fa-eraser"></i> Purge Category</button>
 						</div>
@@ -120,37 +128,6 @@
 					<!-- IMPORT admin/partials/categories/privileges.tpl -->
 				</div>
 			</div>
-
-			<div class="tab-pane fade col-xs-12" id="analytics">
-				<div class="row">
-					<div class="col-sm-6 text-center">
-						<div><canvas id="pageviews:hourly" height="250"></canvas></div>
-						<p>
-							<small><strong>Figure 1</strong> &ndash; Hourly page views for this category</small>
-						</p>
-					</div>
-					<div class="col-sm-6 text-center">
-						<div><canvas id="pageviews:daily" height="250"></canvas></div>
-						<p>
-							<small><strong>Figure 2</strong> &ndash; Daily page views for this category</small>
-						</p>
-					</div>
-				</div>
-				<div class="row">
-					<div class="col-sm-6 text-center">
-						<div><canvas id="topics:daily" height="250"></canvas></div>
-						<p>
-							<small><strong>Figure 3</strong> &ndash; Daily topics created in this category</small>
-						</p>
-					</div>
-					<div class="col-sm-6 text-center">
-						<div><canvas id="posts:daily" height="250"></canvas></div>
-						<p>
-							<small><strong>Figure 4</strong> &ndash; Daily posts made in this category</small>
-						</p>
-					</div>
-				</div>
-			</div>
 		</div>
 	</form>
 </div>
diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl
index 6370630c20..d5807ce88e 100644
--- a/src/views/admin/manage/flags.tpl
+++ b/src/views/admin/manage/flags.tpl
@@ -23,7 +23,7 @@
 					</div>
 				</div>
 
-				<button type="submit" class="btn btn-primary">[[global:search]]</button>
+				<button type="submit" class="btn btn-primary">Search</button>
 			</form>
 			<br />
 			<hr/>
@@ -57,7 +57,7 @@
 								</div>
 								<small>
 									<span class="pull-right">
-										Posted in <a href="{config.relative_path}/category/{posts.category.slug}" target="_blank"><i class="fa {posts.category.icon}"></i> {posts.category.name}</a>, <span class="timeago" title="{posts.relativeTime}"></span> &bull;
+										Posted in <a href="{config.relative_path}/category/{posts.category.slug}" target="_blank"><i class="fa {posts.category.icon}"></i> {posts.category.name}</a>, <span class="timeago" title="{posts.timestampISO}"></span> &bull;
 										<a href="{config.relative_path}/topic/{posts.topic.slug}/{posts.index}" target="_blank">Read More</a>
 									</span>
 								</small>
diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl
index 28cd6c006e..a50fdba51b 100644
--- a/src/views/admin/manage/group.tpl
+++ b/src/views/admin/manage/group.tpl
@@ -9,12 +9,12 @@
 
 				<fieldset>
 					<label for="change-group-desc">Description</label>
-					<input type="text" class="form-control" id="change-group-desc" placeholder="A short description about your group" value="{group.description}" /><br />
+					<input type="text" class="form-control" id="change-group-desc" placeholder="A short description about your group" value="{group.description}" maxlength="255" /><br />
 				</fieldset>
 
 				<fieldset>
 					<label for="change-group-user-title">Title of Members</label>
-					<input type="text" class="form-control" id="change-group-user-title" placeholder="The title of users if they are a member of this group" value="{group.userTitle}"/><br />
+					<input type="text" class="form-control" id="change-group-user-title" placeholder="The title of users if they are a member of this group" value="{group.userTitle}" maxlength="40" /><br />
 				</fieldset>
 
 				<fieldset>
@@ -31,7 +31,7 @@
 				<fieldset>
 					<div class="checkbox">
 						<label>
-							<input id="group-userTitleEnabled" name="userTitleEnabled" type="checkbox"<!-- IF group.userTitleEnabled --> checked<!-- ENDIF group.userTitleEnabled -->> <strong>[[groups:details.userTitleEnabled]]</strong>
+							<input id="group-userTitleEnabled" name="userTitleEnabled" type="checkbox"<!-- IF group.userTitleEnabled --> checked<!-- ENDIF group.userTitleEnabled -->> <strong>Show Badge</strong>
 						</label>
 					</div>
 				</fieldset>
@@ -41,8 +41,13 @@
 						<label>
 							<input id="group-private" name="private" type="checkbox"<!-- IF group.private --> checked<!-- ENDIF group.private -->> <strong>[[groups:details.private]]</strong>
 							<p class="help-block">
-								[[groups:details.private_help]]
+								If enabled, joining of groups requires approval from a group owner.
 							</p>
+							<!-- IF !allowPrivateGroups -->
+							<p class="help-block">
+								Warning: Private groups is disabled at system level, which overrides this option.
+							</p>
+							<!-- ENDIF !allowPrivateGroups -->
 						</label>
 					</div>
 				</fieldset>
@@ -50,7 +55,7 @@
 				<fieldset>
 					<div class="checkbox">
 						<label>
-							<input id="group-disableJoinRequests" name="disableJoinRequests" type="checkbox"<!-- IF group.disableJoinRequests --> checked<!-- ENDIF group.disableJoinRequests -->> <strong>[[groups:details.disableJoinRequests]]</strong>
+							<input id="group-disableJoinRequests" name="disableJoinRequests" type="checkbox"<!-- IF group.disableJoinRequests --> checked<!-- ENDIF group.disableJoinRequests -->> <strong>Disable join requests</strong>
 						</label>
 					</div>
 				</fieldset>
@@ -58,9 +63,9 @@
 				<fieldset>
 					<div class="checkbox">
 						<label>
-							<input id="group-hidden" name="hidden" type="checkbox"<!-- IF group.hidden --> checked<!-- ENDIF group.hidden -->> <strong>[[groups:details.hidden]]</strong>
+							<input id="group-hidden" name="hidden" type="checkbox"<!-- IF group.hidden --> checked<!-- ENDIF group.hidden -->> <strong>Hidden</strong>
 							<p class="help-block">
-								[[groups:details.hidden_help]]
+								If enabled, this group will not be found in the groups listing, and users will have to be invited manually
 							</p>
 						</label>
 					</div>
@@ -76,7 +81,7 @@
 				<fieldset>
 					<div class="panel panel-default">
 						<div class="panel-heading">
-							<h3 class="panel-title"><i class="fa fa-users"></i> [[groups:details.members]]</h3>
+							<h3 class="panel-title"><i class="fa fa-users"></i> Member List</h3>
 						</div>
 						<div class="panel-body">
 							<!-- IMPORT partials/groups/memberlist.tpl -->
diff --git a/src/views/admin/manage/ip-blacklist.tpl b/src/views/admin/manage/ip-blacklist.tpl
new file mode 100644
index 0000000000..26170b286e
--- /dev/null
+++ b/src/views/admin/manage/ip-blacklist.tpl
@@ -0,0 +1,40 @@
+<div class="flags">
+	<div class="col-lg-12">
+		<p class="lead">
+			Configure your IP blacklist here.
+		</p>
+		<p>
+			Occasionally, a user account ban is not enough of a deterrant. Other times, restricting access to the forum to a specific IP or a range of IPs
+			is the best way to protect a forum. In these scenarios, you can add troublesome IP addresses or entire CIDR blocks to this blacklist, and
+			they will be prevented from logging in to or registering a new account.
+		</p>
+
+		<div class="row">
+			<div class="col-sm-6">
+				<textarea id="blacklist-rules">{rules}</textarea>
+			</div>
+			<div class="col-sm-6">
+				<div class="panel panel-default">
+					<div class="panel-heading">Active Rules</div>
+					<div class="panel-body">
+						<button type="button" class="btn btn-warning" data-action="test"><i class="fa fa-bomb"></i> Validate Blacklist</button>
+						<button type="button" class="btn btn-primary" data-action="apply"><i class="fa fa-save"></i> Apply Blacklist</button>
+					</div>
+				</div>
+				<div class="panel panel-default">
+					<div class="panel-heading">Syntax Hints</div>
+					<div class="panel-body">
+						<p>
+							Define a single IP addresses per line. You can add IP blocks as long as they follow the CIDR format (e.g.
+							<code>192.168.100.0/22</code>).
+						</p>
+						<p>
+							You can add in comments by starting lines with the <code>#</code> symbol.
+						</p>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+
+</div>
\ No newline at end of file
diff --git a/src/views/admin/manage/registration.tpl b/src/views/admin/manage/registration.tpl
index 278ba279b1..d293d8cfa4 100644
--- a/src/views/admin/manage/registration.tpl
+++ b/src/views/admin/manage/registration.tpl
@@ -44,7 +44,7 @@
 				{users.ip}
 			</td>
 			<td>
-				<span class="timeago" title="{users.timestamp}"></span>
+				<span class="timeago" title="{users.timestampISO}"></span>
 			</td>
 			<td>
 				<div class="btn-group pull-right">
@@ -55,6 +55,8 @@
 		</tr>
 		<!-- END users -->
 	</table>
+
+	<!-- IMPORT partials/paginator.tpl -->
 </div>
 
 <div class="invitations panel panel-success">
@@ -66,18 +68,23 @@
 		<br><br>
 		The username will be displayed to the right of the emails for users who have redeemed their invitations.
 	</p>
-	<table class="table table-striped users-list">
+	<table class="table table-striped invites-list">
 		<tr>
 			<th>Inviter Username</th>
 			<th>Invitee Email</th>
 			<th>Invitee Username (if registered)</th>
 		</tr>
 		<!-- BEGIN invites -->
-		<tr>
-			<!-- BEGIN invites.invitations -->
-			<td><!-- IF @first -->{invites.username}<!-- ENDIF @first --></td>
+		<!-- BEGIN invites.invitations -->
+		<tr data-invitation-mail="{invites.invitations.email}"
+				data-invited-by="{invites.username}">
+			<td class ="invited-by"><!-- IF @first -->{invites.username}<!-- ENDIF @first --></td>
 			<td>{invites.invitations.email}</td>
-			<td>{invites.invitations.username}</td>
+			<td>{invites.invitations.username}
+				<div class="btn-group pull-right">
+					<button class="btn btn-danger btn-xs" data-action="delete"><i class="fa fa-times"></i></button>
+				</div>
+			</td>
 		</tr>
 		<!-- END invites.invitations -->
 		<!-- END invites -->
diff --git a/src/views/admin/partials/blacklist-validate.tpl b/src/views/admin/partials/blacklist-validate.tpl
new file mode 100644
index 0000000000..4a642a23a3
--- /dev/null
+++ b/src/views/admin/partials/blacklist-validate.tpl
@@ -0,0 +1,14 @@
+<p class="lead">
+	<strong>{valid.length}</strong> out of <strong>{numRules}</strong> rule(s) valid.
+</p>
+
+<!-- IF invalid.length -->
+<p>
+	The following <strong>{invalid.length}</strong> rules are invalid:
+</p>
+<ul>
+	<!-- BEGIN invalid -->
+	<li><code>@value</code></li>
+	<!-- END invalid -->
+</ul>
+<!-- ENDIF invalid.length -->
\ No newline at end of file
diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl
index a48ec5a327..13e902fb99 100644
--- a/src/views/admin/partials/categories/category-rows.tpl
+++ b/src/views/admin/partials/categories/category-rows.tpl
@@ -19,6 +19,7 @@
                         <button data-cid="{categories.cid}" data-action="toggle" data-disabled="{categories.disabled}" class="btn <!-- IF categories.disabled -->btn-primary<!-- ELSE -->btn-danger<!-- ENDIF categories.disabled -->">
                             <!-- IF categories.disabled -->Enable<!-- ELSE -->Disable<!-- ENDIF categories.disabled -->
                         </button>
+                        <a href="./categories/{categories.cid}/analytics" class="btn btn-default"><i class="fa fa-line-chart"></i></a>
                         <a href="./categories/{categories.cid}" class="btn btn-default">Edit</a>
                     </div>
                 </div>
diff --git a/src/views/admin/partials/categories/create.tpl b/src/views/admin/partials/categories/create.tpl
index 52e218fc0e..d4c551f1bf 100644
--- a/src/views/admin/partials/categories/create.tpl
+++ b/src/views/admin/partials/categories/create.tpl
@@ -12,4 +12,14 @@
 			<!-- END categories -->
 		</select>
 	</div>
+
+	<div class="form-group">
+		<label for="cloneFromCid">(Optional) Clone Settings From Category</label>
+		<select class="form-control" name="cloneFromCid" id="cloneFromCid">
+			<option value=""></option>
+			<!-- BEGIN categories -->
+			<option value="{categories.cid}">{categories.name}</option>
+			<!-- END categories -->
+		</select>
+	</div>
 </form>
\ No newline at end of file
diff --git a/src/views/admin/partials/categories/select-category.tpl b/src/views/admin/partials/categories/select-category.tpl
new file mode 100644
index 0000000000..7e1f9f0d28
--- /dev/null
+++ b/src/views/admin/partials/categories/select-category.tpl
@@ -0,0 +1,10 @@
+<form type="form">
+	<div class="form-group">
+		<label for="select-cid">Select Category</label>
+		<select class="form-control" name="select-cid" id="select-cid">
+			<!-- BEGIN categories -->
+			<option value="{categories.cid}">{categories.name}</option>
+			<!-- END categories -->
+		</select>
+	</div>
+</form>
\ No newline at end of file
diff --git a/src/views/admin/partials/menu.tpl b/src/views/admin/partials/menu.tpl
index a0027c0621..8d0f0f853d 100644
--- a/src/views/admin/partials/menu.tpl
+++ b/src/views/admin/partials/menu.tpl
@@ -20,6 +20,7 @@
 			<li><a href="{relative_path}/admin/manage/registration">Registration Queue</a></li>
 			<li><a href="{relative_path}/admin/manage/groups">Groups</a></li>
 			<li><a href="{relative_path}/admin/manage/flags">Flags</a></li>
+			<li><a href="{relative_path}/admin/manage/ip-blacklist">IP Blacklist</a></li>
 		</ul>
 	</section>
 
@@ -173,6 +174,7 @@
 					<li><a href="{relative_path}/admin/manage/registration">Registration Queue</a></li>
 					<li><a href="{relative_path}/admin/manage/groups">Groups</a></li>
 					<li><a href="{relative_path}/admin/manage/flags">Flags</a></li>
+					<li><a href="{relative_path}/admin/manage/ip-blacklist">IP Blacklist</a></li>
 				</ul>
 			</li>
 			<li class="dropdown menu-item">
diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl
index 147657195e..fa41b38f61 100644
--- a/src/views/admin/settings/general.tpl
+++ b/src/views/admin/settings/general.tpl
@@ -102,7 +102,7 @@
 </div>
 
 <div class="row">
-	<div class="col-sm-2 col-xs-12 settings-header">Miscellaneous</div>
+	<div class="col-sm-2 col-xs-12 settings-header">Outgoing Links</div>
 	<div class="col-sm-10 col-xs-12">
 		<form>
 			<div class="checkbox">
@@ -111,13 +111,6 @@
 					<span class="mdl-switch__label"><strong>Use Outgoing Links Warning Page</strong></span>
 				</label>
 			</div>
-			<div class="checkbox">
-				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
-					<input type="checkbox" class="mdl-switch__input" id="showSiteTitle" data-field="disableSocialButtons">
-					<span class="mdl-switch__label"><strong>Disable social buttons</strong></span>
-				</label>
-			</div>
-
 		</form>
 	</div>
 </div>
diff --git a/src/views/admin/settings/user.tpl b/src/views/admin/settings/user.tpl
index cf5360d5fb..55c54149c6 100644
--- a/src/views/admin/settings/user.tpl
+++ b/src/views/admin/settings/user.tpl
@@ -72,6 +72,12 @@
 					<span class="mdl-switch__label"><strong>Disable email changes</strong></span>
 				</label>
 			</div>
+			<div class="checkbox">
+				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
+					<input class="mdl-switch__input" type="checkbox" data-field="password:disableEdit">
+					<span class="mdl-switch__label"><strong>Disable password changes</strong></span>
+				</label>
+			</div>
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="allowAccountDelete" checked>
@@ -178,40 +184,40 @@
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="showemail">
-					<span class="mdl-switch__label"><strong>[[user:show_email]]</strong></span>
+					<span class="mdl-switch__label"><strong>Show email</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="showfullname">
-					<span class="mdl-switch__label"><strong>[[user:show_fullname]]</strong></span>
+					<span class="mdl-switch__label"><strong>Show fullname</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="restrictChat">
-					<span class="mdl-switch__label"><strong>[[user:restrict_chats]]</strong></span>
+					<span class="mdl-switch__label"><strong>Only allow chat messages from users I follow</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="openOutgoingLinksInNewTab">
-					<span class="mdl-switch__label"><strong>[[user:open_links_in_new_tab]]</strong></span>
+					<span class="mdl-switch__label"><strong>Open outgoing links in new tab</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="topicSearchEnabled">
-					<span class="mdl-switch__label"><strong>[[user:enable_topic_searching]]</strong></span>
+					<span class="mdl-switch__label"><strong>Enable In-Topic Searching</strong></span>
 				</label>
 			</div>
 
 			<div class="form-group">
-				<label>[[user:digest_label]]</label>
+				<label>Subscribe to Digest</label>
 				<select class="form-control" data-field="dailyDigestFreq">
 					<option value="off">Off</option>
 					<option value="day">Daily</option>
@@ -223,35 +229,35 @@
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="sendChatNotifications">
-					<span class="mdl-switch__label"><strong>[[user:send_chat_notifications]]</strong></span>
+					<span class="mdl-switch__label"><strong>Send an email if a new chat message arrives and I am not online</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="sendPostNotifications">
-					<span class="mdl-switch__label"><strong>[[user:send_post_notifications]]</strong></span>
+					<span class="mdl-switch__label"><strong>Send an email when replies are made to topics I am subscribed to</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="followTopicsOnCreate">
-					<span class="mdl-switch__label"><strong>[[user:follow_topics_you_create]]</strong></span>
+					<span class="mdl-switch__label"><strong>Follow topics you create</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="followTopicsOnReply">
-					<span class="mdl-switch__label"><strong>[[user:follow_topics_you_reply_to]]</strong></span>
+					<span class="mdl-switch__label"><strong>Follow topics that you reply to</strong></span>
 				</label>
 			</div>
 
 			<div class="checkbox">
 				<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
 					<input class="mdl-switch__input" type="checkbox" data-field="notificationSounds" />
-					<span class="mdl-switch__label">[[user:notification_sounds]]</span>
+					<span class="mdl-switch__label">Play a sound when you receive a notification</span>
 				</label>
 			</div>
 
diff --git a/src/views/partials/fontawesome.tpl b/src/views/partials/fontawesome.tpl
index 6ede4e70d3..a179bfdd49 100644
--- a/src/views/partials/fontawesome.tpl
+++ b/src/views/partials/fontawesome.tpl
@@ -4,7 +4,7 @@
 		<input type="text" class="form-control" id="fa-filter" data-action="filter" placeholder="e.g. umbrella" />
 	</div>
 	<div class="row fa-icons">
-		<i class="fa fa-adjust"></i><i class="fa fa-adn"></i><i class="fa fa-align-center"></i><i class="fa fa-align-justify"></i><i class="fa fa-align-left"></i><i class="fa fa-align-right"></i><i class="fa fa-ambulance"></i><i class="fa fa-anchor"></i><i class="fa fa-android"></i><i class="fa fa-angellist"></i><i class="fa fa-angle-double-down"></i><i class="fa fa-angle-double-left"></i><i class="fa fa-angle-double-right"></i><i class="fa fa-angle-double-up"></i><i class="fa fa-angle-down"></i><i class="fa fa-angle-left"></i><i class="fa fa-angle-right"></i><i class="fa fa-angle-up"></i><i class="fa fa-apple"></i><i class="fa fa-archive"></i><i class="fa fa-area-chart"></i><i class="fa fa-arrow-circle-down"></i><i class="fa fa-arrow-circle-left"></i><i class="fa fa-arrow-circle-o-down"></i><i class="fa fa-arrow-circle-o-left"></i><i class="fa fa-arrow-circle-o-right"></i><i class="fa fa-arrow-circle-o-up"></i><i class="fa fa-arrow-circle-right"></i><i class="fa fa-arrow-circle-up"></i><i class="fa fa-arrow-down"></i><i class="fa fa-arrow-left"></i><i class="fa fa-arrow-right"></i><i class="fa fa-arrow-up"></i><i class="fa fa-arrows"></i><i class="fa fa-arrows-alt"></i><i class="fa fa-arrows-h"></i><i class="fa fa-arrows-v"></i><i class="fa fa-asterisk"></i><i class="fa fa-at"></i><i class="fa fa-backward"></i><i class="fa fa-ban"></i><i class="fa fa-bar-chart"></i><i class="fa fa-barcode"></i><i class="fa fa-bars"></i><i class="fa fa-beer"></i><i class="fa fa-behance"></i><i class="fa fa-behance-square"></i><i class="fa fa-bell"></i><i class="fa fa-bell-o"></i><i class="fa fa-bell-slash"></i><i class="fa fa-bell-slash-o"></i><i class="fa fa-bicycle"></i><i class="fa fa-binoculars"></i><i class="fa fa-birthday-cake"></i><i class="fa fa-bitbucket"></i><i class="fa fa-bitbucket-square"></i><i class="fa fa-bold"></i><i class="fa fa-bolt"></i><i class="fa fa-bomb"></i><i class="fa fa-book"></i><i class="fa fa-bookmark"></i><i class="fa fa-bookmark-o"></i><i class="fa fa-briefcase"></i><i class="fa fa-btc"></i><i class="fa fa-bug"></i><i class="fa fa-building"></i><i class="fa fa-building-o"></i><i class="fa fa-bullhorn"></i><i class="fa fa-bullseye"></i><i class="fa fa-bus"></i><i class="fa fa-calculator"></i><i class="fa fa-calendar"></i><i class="fa fa-calendar-o"></i><i class="fa fa-camera"></i><i class="fa fa-camera-retro"></i><i class="fa fa-car"></i><i class="fa fa-caret-down"></i><i class="fa fa-caret-left"></i><i class="fa fa-caret-right"></i><i class="fa fa-caret-square-o-down"></i><i class="fa fa-caret-square-o-left"></i><i class="fa fa-caret-square-o-right"></i><i class="fa fa-caret-square-o-up"></i><i class="fa fa-caret-up"></i><i class="fa fa-cc"></i><i class="fa fa-cc-amex"></i><i class="fa fa-cc-discover"></i><i class="fa fa-cc-mastercard"></i><i class="fa fa-cc-paypal"></i><i class="fa fa-cc-stripe"></i><i class="fa fa-cc-visa"></i><i class="fa fa-certificate"></i><i class="fa fa-chain-broken"></i><i class="fa fa-check"></i><i class="fa fa-check-circle"></i><i class="fa fa-check-circle-o"></i><i class="fa fa-check-square"></i><i class="fa fa-check-square-o"></i><i class="fa fa-chevron-circle-down"></i><i class="fa fa-chevron-circle-left"></i><i class="fa fa-chevron-circle-right"></i><i class="fa fa-chevron-circle-up"></i><i class="fa fa-chevron-down"></i><i class="fa fa-chevron-left"></i><i class="fa fa-chevron-right"></i><i class="fa fa-chevron-up"></i><i class="fa fa-child"></i><i class="fa fa-circle"></i><i class="fa fa-circle-o"></i><i class="fa fa-circle-o-notch"></i><i class="fa fa-circle-thin"></i><i class="fa fa-clipboard"></i><i class="fa fa-clock-o"></i><i class="fa fa-cloud"></i><i class="fa fa-cloud-download"></i><i class="fa fa-cloud-upload"></i><i class="fa fa-code"></i><i class="fa fa-code-fork"></i><i class="fa fa-codepen"></i><i class="fa fa-coffee"></i><i class="fa fa-cog"></i><i class="fa fa-cogs"></i><i class="fa fa-columns"></i><i class="fa fa-comment"></i><i class="fa fa-comment-o"></i><i class="fa fa-comments"></i><i class="fa fa-comments-o"></i><i class="fa fa-compass"></i><i class="fa fa-compress"></i><i class="fa fa-copyright"></i><i class="fa fa-credit-card"></i><i class="fa fa-crop"></i><i class="fa fa-crosshairs"></i><i class="fa fa-css3"></i><i class="fa fa-cube"></i><i class="fa fa-cubes"></i><i class="fa fa-cutlery"></i><i class="fa fa-database"></i><i class="fa fa-delicious"></i><i class="fa fa-desktop"></i><i class="fa fa-deviantart"></i><i class="fa fa-digg"></i><i class="fa fa-dot-circle-o"></i><i class="fa fa-download"></i><i class="fa fa-dribbble"></i><i class="fa fa-dropbox"></i><i class="fa fa-drupal"></i><i class="fa fa-eject"></i><i class="fa fa-ellipsis-h"></i><i class="fa fa-ellipsis-v"></i><i class="fa fa-empire"></i><i class="fa fa-envelope"></i><i class="fa fa-envelope-o"></i><i class="fa fa-envelope-square"></i><i class="fa fa-eraser"></i><i class="fa fa-eur"></i><i class="fa fa-exchange"></i><i class="fa fa-exclamation"></i><i class="fa fa-exclamation-circle"></i><i class="fa fa-exclamation-triangle"></i><i class="fa fa-expand"></i><i class="fa fa-external-link"></i><i class="fa fa-external-link-square"></i><i class="fa fa-eye"></i><i class="fa fa-eye-slash"></i><i class="fa fa-eyedropper"></i><i class="fa fa-facebook"></i><i class="fa fa-facebook-square"></i><i class="fa fa-fast-backward"></i><i class="fa fa-fast-forward"></i><i class="fa fa-fax"></i><i class="fa fa-female"></i><i class="fa fa-fighter-jet"></i><i class="fa fa-file"></i><i class="fa fa-file-archive-o"></i><i class="fa fa-file-audio-o"></i><i class="fa fa-file-code-o"></i><i class="fa fa-file-excel-o"></i><i class="fa fa-file-image-o"></i><i class="fa fa-file-o"></i><i class="fa fa-file-pdf-o"></i><i class="fa fa-file-powerpoint-o"></i><i class="fa fa-file-text"></i><i class="fa fa-file-text-o"></i><i class="fa fa-file-video-o"></i><i class="fa fa-file-word-o"></i><i class="fa fa-files-o"></i><i class="fa fa-film"></i><i class="fa fa-filter"></i><i class="fa fa-fire"></i><i class="fa fa-fire-extinguisher"></i><i class="fa fa-flag"></i><i class="fa fa-flag-checkered"></i><i class="fa fa-flag-o"></i><i class="fa fa-flask"></i><i class="fa fa-flickr"></i><i class="fa fa-floppy-o"></i><i class="fa fa-folder"></i><i class="fa fa-folder-o"></i><i class="fa fa-folder-open"></i><i class="fa fa-folder-open-o"></i><i class="fa fa-font"></i><i class="fa fa-forward"></i><i class="fa fa-foursquare"></i><i class="fa fa-frown-o"></i><i class="fa fa-futbol-o"></i><i class="fa fa-gamepad"></i><i class="fa fa-gavel"></i><i class="fa fa-gbp"></i><i class="fa fa-gift"></i><i class="fa fa-git"></i><i class="fa fa-git-square"></i><i class="fa fa-github"></i><i class="fa fa-github-alt"></i><i class="fa fa-github-square"></i><i class="fa fa-gittip"></i><i class="fa fa-glass"></i><i class="fa fa-globe"></i><i class="fa fa-google"></i><i class="fa fa-google-plus"></i><i class="fa fa-google-plus-square"></i><i class="fa fa-google-wallet"></i><i class="fa fa-graduation-cap"></i><i class="fa fa-h-square"></i><i class="fa fa-hacker-news"></i><i class="fa fa-hand-o-down"></i><i class="fa fa-hand-o-left"></i><i class="fa fa-hand-o-right"></i><i class="fa fa-hand-o-up"></i><i class="fa fa-hdd-o"></i><i class="fa fa-header"></i><i class="fa fa-headphones"></i><i class="fa fa-heart"></i><i class="fa fa-heart-o"></i><i class="fa fa-history"></i><i class="fa fa-home"></i><i class="fa fa-hospital-o"></i><i class="fa fa-html5"></i><i class="fa fa-ils"></i><i class="fa fa-inbox"></i><i class="fa fa-indent"></i><i class="fa fa-info"></i><i class="fa fa-info-circle"></i><i class="fa fa-inr"></i><i class="fa fa-instagram"></i><i class="fa fa-ioxhost"></i><i class="fa fa-italic"></i><i class="fa fa-joomla"></i><i class="fa fa-jpy"></i><i class="fa fa-jsfiddle"></i><i class="fa fa-key"></i><i class="fa fa-keyboard-o"></i><i class="fa fa-krw"></i><i class="fa fa-language"></i><i class="fa fa-laptop"></i><i class="fa fa-lastfm"></i><i class="fa fa-lastfm-square"></i><i class="fa fa-leaf"></i><i class="fa fa-lemon-o"></i><i class="fa fa-level-down"></i><i class="fa fa-level-up"></i><i class="fa fa-life-ring"></i><i class="fa fa-lightbulb-o"></i><i class="fa fa-line-chart"></i><i class="fa fa-link"></i><i class="fa fa-linkedin"></i><i class="fa fa-linkedin-square"></i><i class="fa fa-linux"></i><i class="fa fa-list"></i><i class="fa fa-list-alt"></i><i class="fa fa-list-ol"></i><i class="fa fa-list-ul"></i><i class="fa fa-location-arrow"></i><i class="fa fa-lock"></i><i class="fa fa-long-arrow-down"></i><i class="fa fa-long-arrow-left"></i><i class="fa fa-long-arrow-right"></i><i class="fa fa-long-arrow-up"></i><i class="fa fa-magic"></i><i class="fa fa-magnet"></i><i class="fa fa-male"></i><i class="fa fa-map-marker"></i><i class="fa fa-maxcdn"></i><i class="fa fa-meanpath"></i><i class="fa fa-medkit"></i><i class="fa fa-meh-o"></i><i class="fa fa-microphone"></i><i class="fa fa-microphone-slash"></i><i class="fa fa-minus"></i><i class="fa fa-minus-circle"></i><i class="fa fa-minus-square"></i><i class="fa fa-minus-square-o"></i><i class="fa fa-mobile"></i><i class="fa fa-money"></i><i class="fa fa-moon-o"></i><i class="fa fa-music"></i><i class="fa fa-newspaper-o"></i><i class="fa fa-openid"></i><i class="fa fa-outdent"></i><i class="fa fa-pagelines"></i><i class="fa fa-paint-brush"></i><i class="fa fa-paper-plane"></i><i class="fa fa-paper-plane-o"></i><i class="fa fa-paperclip"></i><i class="fa fa-paragraph"></i><i class="fa fa-pause"></i><i class="fa fa-paw"></i><i class="fa fa-paypal"></i><i class="fa fa-pencil"></i><i class="fa fa-pencil-square"></i><i class="fa fa-pencil-square-o"></i><i class="fa fa-phone"></i><i class="fa fa-phone-square"></i><i class="fa fa-picture-o"></i><i class="fa fa-pie-chart"></i><i class="fa fa-pied-piper"></i><i class="fa fa-pied-piper-alt"></i><i class="fa fa-pinterest"></i><i class="fa fa-pinterest-square"></i><i class="fa fa-plane"></i><i class="fa fa-play"></i><i class="fa fa-play-circle"></i><i class="fa fa-play-circle-o"></i><i class="fa fa-plug"></i><i class="fa fa-plus"></i><i class="fa fa-plus-circle"></i><i class="fa fa-plus-square"></i><i class="fa fa-plus-square-o"></i><i class="fa fa-power-off"></i><i class="fa fa-print"></i><i class="fa fa-puzzle-piece"></i><i class="fa fa-qq"></i><i class="fa fa-qrcode"></i><i class="fa fa-question"></i><i class="fa fa-question-circle"></i><i class="fa fa-quote-left"></i><i class="fa fa-quote-right"></i><i class="fa fa-random"></i><i class="fa fa-rebel"></i><i class="fa fa-recycle"></i><i class="fa fa-reddit"></i><i class="fa fa-reddit-square"></i><i class="fa fa-refresh"></i><i class="fa fa-renren"></i><i class="fa fa-repeat"></i><i class="fa fa-reply"></i><i class="fa fa-reply-all"></i><i class="fa fa-retweet"></i><i class="fa fa-road"></i><i class="fa fa-rocket"></i><i class="fa fa-rss"></i><i class="fa fa-rss-square"></i><i class="fa fa-rub"></i><i class="fa fa-scissors"></i><i class="fa fa-search"></i><i class="fa fa-search-minus"></i><i class="fa fa-search-plus"></i><i class="fa fa-share"></i><i class="fa fa-share-alt"></i><i class="fa fa-share-alt-square"></i><i class="fa fa-share-square"></i><i class="fa fa-share-square-o"></i><i class="fa fa-shield"></i><i class="fa fa-shopping-cart"></i><i class="fa fa-sign-in"></i><i class="fa fa-sign-out"></i><i class="fa fa-signal"></i><i class="fa fa-sitemap"></i><i class="fa fa-skype"></i><i class="fa fa-slack"></i><i class="fa fa-sliders"></i><i class="fa fa-slideshare"></i><i class="fa fa-smile-o"></i><i class="fa fa-sort"></i><i class="fa fa-sort-alpha-asc"></i><i class="fa fa-sort-alpha-desc"></i><i class="fa fa-sort-amount-asc"></i><i class="fa fa-sort-amount-desc"></i><i class="fa fa-sort-asc"></i><i class="fa fa-sort-desc"></i><i class="fa fa-sort-numeric-asc"></i><i class="fa fa-sort-numeric-desc"></i><i class="fa fa-soundcloud"></i><i class="fa fa-space-shuttle"></i><i class="fa fa-spinner"></i><i class="fa fa-spoon"></i><i class="fa fa-spotify"></i><i class="fa fa-square"></i><i class="fa fa-square-o"></i><i class="fa fa-stack-exchange"></i><i class="fa fa-stack-overflow"></i><i class="fa fa-star"></i><i class="fa fa-star-half"></i><i class="fa fa-star-half-o"></i><i class="fa fa-star-o"></i><i class="fa fa-steam"></i><i class="fa fa-steam-square"></i><i class="fa fa-step-backward"></i><i class="fa fa-step-forward"></i><i class="fa fa-stethoscope"></i><i class="fa fa-stop"></i><i class="fa fa-strikethrough"></i><i class="fa fa-stumbleupon"></i><i class="fa fa-stumbleupon-circle"></i><i class="fa fa-subscript"></i><i class="fa fa-suitcase"></i><i class="fa fa-sun-o"></i><i class="fa fa-superscript"></i><i class="fa fa-table"></i><i class="fa fa-tablet"></i><i class="fa fa-tachometer"></i><i class="fa fa-tag"></i><i class="fa fa-tags"></i><i class="fa fa-tasks"></i><i class="fa fa-taxi"></i><i class="fa fa-tencent-weibo"></i><i class="fa fa-terminal"></i><i class="fa fa-text-height"></i><i class="fa fa-text-width"></i><i class="fa fa-th"></i><i class="fa fa-th-large"></i><i class="fa fa-th-list"></i><i class="fa fa-thumb-tack"></i><i class="fa fa-thumbs-down"></i><i class="fa fa-thumbs-o-down"></i><i class="fa fa-thumbs-o-up"></i><i class="fa fa-thumbs-up"></i><i class="fa fa-ticket"></i><i class="fa fa-times"></i><i class="fa fa-times-circle"></i><i class="fa fa-times-circle-o"></i><i class="fa fa-tint"></i><i class="fa fa-toggle-off"></i><i class="fa fa-toggle-on"></i><i class="fa fa-trash"></i><i class="fa fa-trash-o"></i><i class="fa fa-tree"></i><i class="fa fa-trello"></i><i class="fa fa-trophy"></i><i class="fa fa-truck"></i><i class="fa fa-try"></i><i class="fa fa-tty"></i><i class="fa fa-tumblr"></i><i class="fa fa-tumblr-square"></i><i class="fa fa-twitch"></i><i class="fa fa-twitter"></i><i class="fa fa-twitter-square"></i><i class="fa fa-umbrella"></i><i class="fa fa-underline"></i><i class="fa fa-undo"></i><i class="fa fa-university"></i><i class="fa fa-unlock"></i><i class="fa fa-unlock-alt"></i><i class="fa fa-upload"></i><i class="fa fa-usd"></i><i class="fa fa-user"></i><i class="fa fa-user-md"></i><i class="fa fa-users"></i><i class="fa fa-video-camera"></i><i class="fa fa-vimeo-square"></i><i class="fa fa-vine"></i><i class="fa fa-vk"></i><i class="fa fa-volume-down"></i><i class="fa fa-volume-off"></i><i class="fa fa-volume-up"></i><i class="fa fa-weibo"></i><i class="fa fa-weixin"></i><i class="fa fa-wheelchair"></i><i class="fa fa-wifi"></i><i class="fa fa-windows"></i><i class="fa fa-wordpress"></i><i class="fa fa-wrench"></i><i class="fa fa-xing"></i><i class="fa fa-xing-square"></i><i class="fa fa-yahoo"></i><i class="fa fa-yelp"></i><i class="fa fa-youtube"></i><i class="fa fa-youtube-play"></i><i class="fa fa-youtube-square"></i>
+		<i class="fa fa-500px"></i> <i class="fa fa-adjust"></i> <i class="fa fa-adn"></i> <i class="fa fa-align-center"></i> <i class="fa fa-align-justify"></i> <i class="fa fa-align-left"></i> <i class="fa fa-align-right"></i> <i class="fa fa-amazon"></i> <i class="fa fa-ambulance"></i> <i class="fa fa-anchor"></i> <i class="fa fa-android"></i> <i class="fa fa-angellist"></i> <i class="fa fa-angle-double-down"></i> <i class="fa fa-angle-double-left"></i> <i class="fa fa-angle-double-right"></i> <i class="fa fa-angle-double-up"></i> <i class="fa fa-angle-down"></i> <i class="fa fa-angle-left"></i> <i class="fa fa-angle-right"></i> <i class="fa fa-angle-up"></i> <i class="fa fa-apple"></i> <i class="fa fa-archive"></i> <i class="fa fa-area-chart"></i> <i class="fa fa-arrow-circle-down"></i> <i class="fa fa-arrow-circle-left"></i> <i class="fa fa-arrow-circle-o-down"></i> <i class="fa fa-arrow-circle-o-left"></i> <i class="fa fa-arrow-circle-o-right"></i> <i class="fa fa-arrow-circle-o-up"></i> <i class="fa fa-arrow-circle-right"></i> <i class="fa fa-arrow-circle-up"></i> <i class="fa fa-arrow-down"></i> <i class="fa fa-arrow-left"></i> <i class="fa fa-arrow-right"></i> <i class="fa fa-arrow-up"></i> <i class="fa fa-arrows"></i> <i class="fa fa-arrows-alt"></i> <i class="fa fa-arrows-h"></i> <i class="fa fa-arrows-v"></i> <i class="fa fa-asterisk"></i> <i class="fa fa-at"></i> <i class="fa fa-automobile"></i> <i class="fa fa-backward"></i> <i class="fa fa-balance-scale"></i> <i class="fa fa-ban"></i> <i class="fa fa-bank"></i> <i class="fa fa-bar-chart"></i> <i class="fa fa-bar-chart-o"></i> <i class="fa fa-barcode"></i> <i class="fa fa-bars"></i> <i class="fa fa-battery-0"></i> <i class="fa fa-battery-1"></i> <i class="fa fa-battery-2"></i> <i class="fa fa-battery-3"></i> <i class="fa fa-battery-4"></i> <i class="fa fa-battery-empty"></i> <i class="fa fa-battery-full"></i> <i class="fa fa-battery-half"></i> <i class="fa fa-battery-quarter"></i> <i class="fa fa-battery-three-quarters"></i> <i class="fa fa-bed"></i> <i class="fa fa-beer"></i> <i class="fa fa-behance"></i> <i class="fa fa-behance-square"></i> <i class="fa fa-bell"></i> <i class="fa fa-bell-o"></i> <i class="fa fa-bell-slash"></i> <i class="fa fa-bell-slash-o"></i> <i class="fa fa-bicycle"></i> <i class="fa fa-binoculars"></i> <i class="fa fa-birthday-cake"></i> <i class="fa fa-bitbucket"></i> <i class="fa fa-bitbucket-square"></i> <i class="fa fa-bitcoin"></i> <i class="fa fa-black-tie"></i> <i class="fa fa-bluetooth"></i> <i class="fa fa-bluetooth-b"></i> <i class="fa fa-bold"></i> <i class="fa fa-bolt"></i> <i class="fa fa-bomb"></i> <i class="fa fa-book"></i> <i class="fa fa-bookmark"></i> <i class="fa fa-bookmark-o"></i> <i class="fa fa-briefcase"></i> <i class="fa fa-btc"></i> <i class="fa fa-bug"></i> <i class="fa fa-building"></i> <i class="fa fa-building-o"></i> <i class="fa fa-bullhorn"></i> <i class="fa fa-bullseye"></i> <i class="fa fa-bus"></i> <i class="fa fa-buysellads"></i> <i class="fa fa-cab"></i> <i class="fa fa-calculator"></i> <i class="fa fa-calendar"></i> <i class="fa fa-calendar-check-o"></i> <i class="fa fa-calendar-minus-o"></i> <i class="fa fa-calendar-o"></i> <i class="fa fa-calendar-plus-o"></i> <i class="fa fa-calendar-times-o"></i> <i class="fa fa-camera"></i> <i class="fa fa-camera-retro"></i> <i class="fa fa-car"></i> <i class="fa fa-caret-down"></i> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-caret-square-o-down"></i> <i class="fa fa-caret-square-o-left"></i> <i class="fa fa-caret-square-o-right"></i> <i class="fa fa-caret-square-o-up"></i> <i class="fa fa-caret-up"></i> <i class="fa fa-cart-arrow-down"></i> <i class="fa fa-cart-plus"></i> <i class="fa fa-cc"></i> <i class="fa fa-cc-amex"></i> <i class="fa fa-cc-diners-club"></i> <i class="fa fa-cc-discover"></i> <i class="fa fa-cc-jcb"></i> <i class="fa fa-cc-mastercard"></i> <i class="fa fa-cc-paypal"></i> <i class="fa fa-cc-stripe"></i> <i class="fa fa-cc-visa"></i> <i class="fa fa-certificate"></i> <i class="fa fa-chain"></i> <i class="fa fa-chain-broken"></i> <i class="fa fa-check"></i> <i class="fa fa-check-circle"></i> <i class="fa fa-check-circle-o"></i> <i class="fa fa-check-square"></i> <i class="fa fa-check-square-o"></i> <i class="fa fa-chevron-circle-down"></i> <i class="fa fa-chevron-circle-left"></i> <i class="fa fa-chevron-circle-right"></i> <i class="fa fa-chevron-circle-up"></i> <i class="fa fa-chevron-down"></i> <i class="fa fa-chevron-left"></i> <i class="fa fa-chevron-right"></i> <i class="fa fa-chevron-up"></i> <i class="fa fa-child"></i> <i class="fa fa-chrome"></i> <i class="fa fa-circle"></i> <i class="fa fa-circle-o"></i> <i class="fa fa-circle-o-notch"></i> <i class="fa fa-circle-thin"></i> <i class="fa fa-clipboard"></i> <i class="fa fa-clock-o"></i> <i class="fa fa-clone"></i> <i class="fa fa-close"></i> <i class="fa fa-cloud"></i> <i class="fa fa-cloud-download"></i> <i class="fa fa-cloud-upload"></i> <i class="fa fa-cny"></i> <i class="fa fa-code"></i> <i class="fa fa-code-fork"></i> <i class="fa fa-codepen"></i> <i class="fa fa-codiepie"></i> <i class="fa fa-coffee"></i> <i class="fa fa-cog"></i> <i class="fa fa-cogs"></i> <i class="fa fa-columns"></i> <i class="fa fa-comment"></i> <i class="fa fa-comment-o"></i> <i class="fa fa-commenting"></i> <i class="fa fa-commenting-o"></i> <i class="fa fa-comments"></i> <i class="fa fa-comments-o"></i> <i class="fa fa-compass"></i> <i class="fa fa-compress"></i> <i class="fa fa-connectdevelop"></i> <i class="fa fa-contao"></i> <i class="fa fa-copy"></i> <i class="fa fa-copyright"></i> <i class="fa fa-creative-commons"></i> <i class="fa fa-credit-card"></i> <i class="fa fa-credit-card-alt"></i> <i class="fa fa-crop"></i> <i class="fa fa-crosshairs"></i> <i class="fa fa-css3"></i> <i class="fa fa-cube"></i> <i class="fa fa-cubes"></i> <i class="fa fa-cut"></i> <i class="fa fa-cutlery"></i> <i class="fa fa-dashboard"></i> <i class="fa fa-dashcube"></i> <i class="fa fa-database"></i> <i class="fa fa-dedent"></i> <i class="fa fa-delicious"></i> <i class="fa fa-desktop"></i> <i class="fa fa-deviantart"></i> <i class="fa fa-diamond"></i> <i class="fa fa-digg"></i> <i class="fa fa-dollar"></i> <i class="fa fa-dot-circle-o"></i> <i class="fa fa-download"></i> <i class="fa fa-dribbble"></i> <i class="fa fa-dropbox"></i> <i class="fa fa-drupal"></i> <i class="fa fa-edge"></i> <i class="fa fa-edit"></i> <i class="fa fa-eject"></i> <i class="fa fa-ellipsis-h"></i> <i class="fa fa-ellipsis-v"></i> <i class="fa fa-empire"></i> <i class="fa fa-envelope"></i> <i class="fa fa-envelope-o"></i> <i class="fa fa-envelope-square"></i> <i class="fa fa-eraser"></i> <i class="fa fa-eur"></i> <i class="fa fa-euro"></i> <i class="fa fa-exchange"></i> <i class="fa fa-exclamation"></i> <i class="fa fa-exclamation-circle"></i> <i class="fa fa-exclamation-triangle"></i> <i class="fa fa-expand"></i> <i class="fa fa-expeditedssl"></i> <i class="fa fa-external-link"></i> <i class="fa fa-external-link-square"></i> <i class="fa fa-eye"></i> <i class="fa fa-eye-slash"></i> <i class="fa fa-eyedropper"></i> <i class="fa fa-facebook"></i> <i class="fa fa-facebook-f"></i> <i class="fa fa-facebook-official"></i> <i class="fa fa-facebook-square"></i> <i class="fa fa-fast-backward"></i> <i class="fa fa-fast-forward"></i> <i class="fa fa-fax"></i> <i class="fa fa-feed"></i> <i class="fa fa-female"></i> <i class="fa fa-fighter-jet"></i> <i class="fa fa-file"></i> <i class="fa fa-file-archive-o"></i> <i class="fa fa-file-audio-o"></i> <i class="fa fa-file-code-o"></i> <i class="fa fa-file-excel-o"></i> <i class="fa fa-file-image-o"></i> <i class="fa fa-file-movie-o"></i> <i class="fa fa-file-o"></i> <i class="fa fa-file-pdf-o"></i> <i class="fa fa-file-photo-o"></i> <i class="fa fa-file-picture-o"></i> <i class="fa fa-file-powerpoint-o"></i> <i class="fa fa-file-sound-o"></i> <i class="fa fa-file-text"></i> <i class="fa fa-file-text-o"></i> <i class="fa fa-file-video-o"></i> <i class="fa fa-file-word-o"></i> <i class="fa fa-file-zip-o"></i> <i class="fa fa-files-o"></i> <i class="fa fa-film"></i> <i class="fa fa-filter"></i> <i class="fa fa-fire"></i> <i class="fa fa-fire-extinguisher"></i> <i class="fa fa-firefox"></i> <i class="fa fa-flag"></i> <i class="fa fa-flag-checkered"></i> <i class="fa fa-flag-o"></i> <i class="fa fa-flash"></i> <i class="fa fa-flask"></i> <i class="fa fa-flickr"></i> <i class="fa fa-floppy-o"></i> <i class="fa fa-folder"></i> <i class="fa fa-folder-o"></i> <i class="fa fa-folder-open"></i> <i class="fa fa-folder-open-o"></i> <i class="fa fa-font"></i> <i class="fa fa-fonticons"></i> <i class="fa fa-fort-awesome"></i> <i class="fa fa-forumbee"></i> <i class="fa fa-forward"></i> <i class="fa fa-foursquare"></i> <i class="fa fa-frown-o"></i> <i class="fa fa-futbol-o"></i> <i class="fa fa-gamepad"></i> <i class="fa fa-gavel"></i> <i class="fa fa-gbp"></i> <i class="fa fa-ge"></i> <i class="fa fa-gear"></i> <i class="fa fa-gears"></i> <i class="fa fa-genderless"></i> <i class="fa fa-get-pocket"></i> <i class="fa fa-gg"></i> <i class="fa fa-gg-circle"></i> <i class="fa fa-gift"></i> <i class="fa fa-git"></i> <i class="fa fa-git-square"></i> <i class="fa fa-github"></i> <i class="fa fa-github-alt"></i> <i class="fa fa-github-square"></i> <i class="fa fa-gittip"></i> <i class="fa fa-glass"></i> <i class="fa fa-globe"></i> <i class="fa fa-google"></i> <i class="fa fa-google-plus"></i> <i class="fa fa-google-plus-square"></i> <i class="fa fa-google-wallet"></i> <i class="fa fa-graduation-cap"></i> <i class="fa fa-gratipay"></i> <i class="fa fa-group"></i> <i class="fa fa-h-square"></i> <i class="fa fa-hacker-news"></i> <i class="fa fa-hand-grab-o"></i> <i class="fa fa-hand-lizard-o"></i> <i class="fa fa-hand-o-down"></i> <i class="fa fa-hand-o-left"></i> <i class="fa fa-hand-o-right"></i> <i class="fa fa-hand-o-up"></i> <i class="fa fa-hand-paper-o"></i> <i class="fa fa-hand-peace-o"></i> <i class="fa fa-hand-pointer-o"></i> <i class="fa fa-hand-rock-o"></i> <i class="fa fa-hand-scissors-o"></i> <i class="fa fa-hand-spock-o"></i> <i class="fa fa-hand-stop-o"></i> <i class="fa fa-hashtag"></i> <i class="fa fa-hdd-o"></i> <i class="fa fa-header"></i> <i class="fa fa-headphones"></i> <i class="fa fa-heart"></i> <i class="fa fa-heart-o"></i> <i class="fa fa-heartbeat"></i> <i class="fa fa-history"></i> <i class="fa fa-home"></i> <i class="fa fa-hospital-o"></i> <i class="fa fa-hotel"></i> <i class="fa fa-hourglass"></i> <i class="fa fa-hourglass-1"></i> <i class="fa fa-hourglass-2"></i> <i class="fa fa-hourglass-3"></i> <i class="fa fa-hourglass-end"></i> <i class="fa fa-hourglass-half"></i> <i class="fa fa-hourglass-o"></i> <i class="fa fa-hourglass-start"></i> <i class="fa fa-houzz"></i> <i class="fa fa-html5"></i> <i class="fa fa-i-cursor"></i> <i class="fa fa-ils"></i> <i class="fa fa-image"></i> <i class="fa fa-inbox"></i> <i class="fa fa-indent"></i> <i class="fa fa-industry"></i> <i class="fa fa-info"></i> <i class="fa fa-info-circle"></i> <i class="fa fa-inr"></i> <i class="fa fa-instagram"></i> <i class="fa fa-institution"></i> <i class="fa fa-internet-explorer"></i> <i class="fa fa-intersex"></i> <i class="fa fa-ioxhost"></i> <i class="fa fa-italic"></i> <i class="fa fa-joomla"></i> <i class="fa fa-jpy"></i> <i class="fa fa-jsfiddle"></i> <i class="fa fa-key"></i> <i class="fa fa-keyboard-o"></i> <i class="fa fa-krw"></i> <i class="fa fa-language"></i> <i class="fa fa-laptop"></i> <i class="fa fa-lastfm"></i> <i class="fa fa-lastfm-square"></i> <i class="fa fa-leaf"></i> <i class="fa fa-leanpub"></i> <i class="fa fa-legal"></i> <i class="fa fa-lemon-o"></i> <i class="fa fa-level-down"></i> <i class="fa fa-level-up"></i> <i class="fa fa-life-bouy"></i> <i class="fa fa-life-buoy"></i> <i class="fa fa-life-ring"></i> <i class="fa fa-life-saver"></i> <i class="fa fa-lightbulb-o"></i> <i class="fa fa-line-chart"></i> <i class="fa fa-link"></i> <i class="fa fa-linkedin"></i> <i class="fa fa-linkedin-square"></i> <i class="fa fa-linux"></i> <i class="fa fa-list"></i> <i class="fa fa-list-alt"></i> <i class="fa fa-list-ol"></i> <i class="fa fa-list-ul"></i> <i class="fa fa-location-arrow"></i> <i class="fa fa-lock"></i> <i class="fa fa-long-arrow-down"></i> <i class="fa fa-long-arrow-left"></i> <i class="fa fa-long-arrow-right"></i> <i class="fa fa-long-arrow-up"></i> <i class="fa fa-magic"></i> <i class="fa fa-magnet"></i> <i class="fa fa-mail-forward"></i> <i class="fa fa-mail-reply"></i> <i class="fa fa-mail-reply-all"></i> <i class="fa fa-male"></i> <i class="fa fa-map"></i> <i class="fa fa-map-marker"></i> <i class="fa fa-map-o"></i> <i class="fa fa-map-pin"></i> <i class="fa fa-map-signs"></i> <i class="fa fa-mars"></i> <i class="fa fa-mars-double"></i> <i class="fa fa-mars-stroke"></i> <i class="fa fa-mars-stroke-h"></i> <i class="fa fa-mars-stroke-v"></i> <i class="fa fa-maxcdn"></i> <i class="fa fa-meanpath"></i> <i class="fa fa-medium"></i> <i class="fa fa-medkit"></i> <i class="fa fa-meh-o"></i> <i class="fa fa-mercury"></i> <i class="fa fa-microphone"></i> <i class="fa fa-microphone-slash"></i> <i class="fa fa-minus"></i> <i class="fa fa-minus-circle"></i> <i class="fa fa-minus-square"></i> <i class="fa fa-minus-square-o"></i> <i class="fa fa-mixcloud"></i> <i class="fa fa-mobile"></i> <i class="fa fa-mobile-phone"></i> <i class="fa fa-modx"></i> <i class="fa fa-money"></i> <i class="fa fa-moon-o"></i> <i class="fa fa-mortar-board"></i> <i class="fa fa-motorcycle"></i> <i class="fa fa-mouse-pointer"></i> <i class="fa fa-music"></i> <i class="fa fa-navicon"></i> <i class="fa fa-neuter"></i> <i class="fa fa-newspaper-o"></i> <i class="fa fa-object-group"></i> <i class="fa fa-object-ungroup"></i> <i class="fa fa-odnoklassniki"></i> <i class="fa fa-odnoklassniki-square"></i> <i class="fa fa-opencart"></i> <i class="fa fa-openid"></i> <i class="fa fa-opera"></i> <i class="fa fa-optin-monster"></i> <i class="fa fa-outdent"></i> <i class="fa fa-pagelines"></i> <i class="fa fa-paint-brush"></i> <i class="fa fa-paper-plane"></i> <i class="fa fa-paper-plane-o"></i> <i class="fa fa-paperclip"></i> <i class="fa fa-paragraph"></i> <i class="fa fa-paste"></i> <i class="fa fa-pause"></i> <i class="fa fa-pause-circle"></i> <i class="fa fa-pause-circle-o"></i> <i class="fa fa-paw"></i> <i class="fa fa-paypal"></i> <i class="fa fa-pencil"></i> <i class="fa fa-pencil-square"></i> <i class="fa fa-pencil-square-o"></i> <i class="fa fa-percent"></i> <i class="fa fa-phone"></i> <i class="fa fa-phone-square"></i> <i class="fa fa-photo"></i> <i class="fa fa-picture-o"></i> <i class="fa fa-pie-chart"></i> <i class="fa fa-pied-piper"></i> <i class="fa fa-pied-piper-alt"></i> <i class="fa fa-pinterest"></i> <i class="fa fa-pinterest-p"></i> <i class="fa fa-pinterest-square"></i> <i class="fa fa-plane"></i> <i class="fa fa-play"></i> <i class="fa fa-play-circle"></i> <i class="fa fa-play-circle-o"></i> <i class="fa fa-plug"></i> <i class="fa fa-plus"></i> <i class="fa fa-plus-circle"></i> <i class="fa fa-plus-square"></i> <i class="fa fa-plus-square-o"></i> <i class="fa fa-power-off"></i> <i class="fa fa-print"></i> <i class="fa fa-product-hunt"></i> <i class="fa fa-puzzle-piece"></i> <i class="fa fa-qq"></i> <i class="fa fa-qrcode"></i> <i class="fa fa-question"></i> <i class="fa fa-question-circle"></i> <i class="fa fa-quote-left"></i> <i class="fa fa-quote-right"></i> <i class="fa fa-ra"></i> <i class="fa fa-random"></i> <i class="fa fa-rebel"></i> <i class="fa fa-recycle"></i> <i class="fa fa-reddit"></i> <i class="fa fa-reddit-alien"></i> <i class="fa fa-reddit-square"></i> <i class="fa fa-refresh"></i> <i class="fa fa-registered"></i> <i class="fa fa-remove"></i> <i class="fa fa-renren"></i> <i class="fa fa-reorder"></i> <i class="fa fa-repeat"></i> <i class="fa fa-reply"></i> <i class="fa fa-reply-all"></i> <i class="fa fa-retweet"></i> <i class="fa fa-rmb"></i> <i class="fa fa-road"></i> <i class="fa fa-rocket"></i> <i class="fa fa-rotate-left"></i> <i class="fa fa-rotate-right"></i> <i class="fa fa-rouble"></i> <i class="fa fa-rss"></i> <i class="fa fa-rss-square"></i> <i class="fa fa-rub"></i> <i class="fa fa-ruble"></i> <i class="fa fa-rupee"></i> <i class="fa fa-safari"></i> <i class="fa fa-save"></i> <i class="fa fa-scissors"></i> <i class="fa fa-scribd"></i> <i class="fa fa-search"></i> <i class="fa fa-search-minus"></i> <i class="fa fa-search-plus"></i> <i class="fa fa-sellsy"></i> <i class="fa fa-send"></i> <i class="fa fa-send-o"></i> <i class="fa fa-server"></i> <i class="fa fa-share"></i> <i class="fa fa-share-alt"></i> <i class="fa fa-share-alt-square"></i> <i class="fa fa-share-square"></i> <i class="fa fa-share-square-o"></i> <i class="fa fa-shekel"></i> <i class="fa fa-sheqel"></i> <i class="fa fa-shield"></i> <i class="fa fa-ship"></i> <i class="fa fa-shirtsinbulk"></i> <i class="fa fa-shopping-bag"></i> <i class="fa fa-shopping-basket"></i> <i class="fa fa-shopping-cart"></i> <i class="fa fa-sign-in"></i> <i class="fa fa-sign-out"></i> <i class="fa fa-signal"></i> <i class="fa fa-simplybuilt"></i> <i class="fa fa-sitemap"></i> <i class="fa fa-skyatlas"></i> <i class="fa fa-skype"></i> <i class="fa fa-slack"></i> <i class="fa fa-sliders"></i> <i class="fa fa-slideshare"></i> <i class="fa fa-smile-o"></i> <i class="fa fa-soccer-ball-o"></i> <i class="fa fa-sort"></i> <i class="fa fa-sort-alpha-asc"></i> <i class="fa fa-sort-alpha-desc"></i> <i class="fa fa-sort-amount-asc"></i> <i class="fa fa-sort-amount-desc"></i> <i class="fa fa-sort-asc"></i> <i class="fa fa-sort-desc"></i> <i class="fa fa-sort-down"></i> <i class="fa fa-sort-numeric-asc"></i> <i class="fa fa-sort-numeric-desc"></i> <i class="fa fa-sort-up"></i> <i class="fa fa-soundcloud"></i> <i class="fa fa-space-shuttle"></i> <i class="fa fa-spinner"></i> <i class="fa fa-spoon"></i> <i class="fa fa-spotify"></i> <i class="fa fa-square"></i> <i class="fa fa-square-o"></i> <i class="fa fa-stack-exchange"></i> <i class="fa fa-stack-overflow"></i> <i class="fa fa-star"></i> <i class="fa fa-star-half"></i> <i class="fa fa-star-half-empty"></i> <i class="fa fa-star-half-full"></i> <i class="fa fa-star-half-o"></i> <i class="fa fa-star-o"></i> <i class="fa fa-steam"></i> <i class="fa fa-steam-square"></i> <i class="fa fa-step-backward"></i> <i class="fa fa-step-forward"></i> <i class="fa fa-stethoscope"></i> <i class="fa fa-sticky-note"></i> <i class="fa fa-sticky-note-o"></i> <i class="fa fa-stop"></i> <i class="fa fa-stop-circle"></i> <i class="fa fa-stop-circle-o"></i> <i class="fa fa-street-view"></i> <i class="fa fa-strikethrough"></i> <i class="fa fa-stumbleupon"></i> <i class="fa fa-stumbleupon-circle"></i> <i class="fa fa-subscript"></i> <i class="fa fa-subway"></i> <i class="fa fa-suitcase"></i> <i class="fa fa-sun-o"></i> <i class="fa fa-superscript"></i> <i class="fa fa-support"></i> <i class="fa fa-table"></i> <i class="fa fa-tablet"></i> <i class="fa fa-tachometer"></i> <i class="fa fa-tag"></i> <i class="fa fa-tags"></i> <i class="fa fa-tasks"></i> <i class="fa fa-taxi"></i> <i class="fa fa-television"></i> <i class="fa fa-tencent-weibo"></i> <i class="fa fa-terminal"></i> <i class="fa fa-text-height"></i> <i class="fa fa-text-width"></i> <i class="fa fa-th"></i> <i class="fa fa-th-large"></i> <i class="fa fa-th-list"></i> <i class="fa fa-thumb-tack"></i> <i class="fa fa-thumbs-down"></i> <i class="fa fa-thumbs-o-down"></i> <i class="fa fa-thumbs-o-up"></i> <i class="fa fa-thumbs-up"></i> <i class="fa fa-ticket"></i> <i class="fa fa-times"></i> <i class="fa fa-times-circle"></i> <i class="fa fa-times-circle-o"></i> <i class="fa fa-tint"></i> <i class="fa fa-toggle-down"></i> <i class="fa fa-toggle-left"></i> <i class="fa fa-toggle-off"></i> <i class="fa fa-toggle-on"></i> <i class="fa fa-toggle-right"></i> <i class="fa fa-toggle-up"></i> <i class="fa fa-trademark"></i> <i class="fa fa-train"></i> <i class="fa fa-transgender"></i> <i class="fa fa-transgender-alt"></i> <i class="fa fa-trash"></i> <i class="fa fa-trash-o"></i> <i class="fa fa-tree"></i> <i class="fa fa-trello"></i> <i class="fa fa-tripadvisor"></i> <i class="fa fa-trophy"></i> <i class="fa fa-truck"></i> <i class="fa fa-try"></i> <i class="fa fa-tty"></i> <i class="fa fa-tumblr"></i> <i class="fa fa-tumblr-square"></i> <i class="fa fa-turkish-lira"></i> <i class="fa fa-tv"></i> <i class="fa fa-twitch"></i> <i class="fa fa-twitter"></i> <i class="fa fa-twitter-square"></i> <i class="fa fa-umbrella"></i> <i class="fa fa-underline"></i> <i class="fa fa-undo"></i> <i class="fa fa-university"></i> <i class="fa fa-unlink"></i> <i class="fa fa-unlock"></i> <i class="fa fa-unlock-alt"></i> <i class="fa fa-unsorted"></i> <i class="fa fa-upload"></i> <i class="fa fa-usb"></i> <i class="fa fa-usd"></i> <i class="fa fa-user"></i> <i class="fa fa-user-md"></i> <i class="fa fa-user-plus"></i> <i class="fa fa-user-secret"></i> <i class="fa fa-user-times"></i> <i class="fa fa-users"></i> <i class="fa fa-venus"></i> <i class="fa fa-venus-double"></i> <i class="fa fa-venus-mars"></i> <i class="fa fa-viacoin"></i> <i class="fa fa-video-camera"></i> <i class="fa fa-vimeo"></i> <i class="fa fa-vimeo-square"></i> <i class="fa fa-vine"></i> <i class="fa fa-vk"></i> <i class="fa fa-volume-down"></i> <i class="fa fa-volume-off"></i> <i class="fa fa-volume-up"></i> <i class="fa fa-warning"></i> <i class="fa fa-wechat"></i> <i class="fa fa-weibo"></i> <i class="fa fa-weixin"></i> <i class="fa fa-whatsapp"></i> <i class="fa fa-wheelchair"></i> <i class="fa fa-wifi"></i> <i class="fa fa-wikipedia-w"></i> <i class="fa fa-windows"></i> <i class="fa fa-won"></i> <i class="fa fa-wordpress"></i> <i class="fa fa-wrench"></i> <i class="fa fa-xing"></i> <i class="fa fa-xing-square"></i> <i class="fa fa-y-combinator"></i> <i class="fa fa-y-combinator-square"></i> <i class="fa fa-yahoo"></i> <i class="fa fa-yc"></i> <i class="fa fa-yc-square"></i> <i class="fa fa-yelp"></i> <i class="fa fa-yen"></i> <i class="fa fa-youtube"></i> <i class="fa fa-youtube-play"></i> <i class="fa fa-youtube-square"></i>
 	</div>
 	<p class="help-block text-center">
 		For a full list of icons, please consult:
diff --git a/src/webserver.js b/src/webserver.js
index cf814feda9..03375c4281 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -78,11 +78,6 @@ function initializeNodeBB(callback) {
 		skipJS = true;
 	}
 
-	if (fromFile.match('less')) {
-		winston.info('[minifier] Compiling LESS files skipped');
-		skipLess = true;
-	}
-
 	async.waterfall([
 		async.apply(cacheStaticFiles),
 		async.apply(meta.themes.setupPaths),
@@ -90,13 +85,14 @@ function initializeNodeBB(callback) {
 			plugins.init(app, middleware, next);
 		},
 		function(next) {
-			async.parallel([
+			async.series([
 				async.apply(meta.templates.compile),
 				async.apply(meta.js.symlinkModules),
 				async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, 'nodebb.min.js'),
 				async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, 'acp.min.js'),
-				async.apply(!skipLess ? meta.css.minify : meta.css.getFromFile),
-				async.apply(meta.sounds.init)
+				async.apply(meta.css.minify),
+				async.apply(meta.sounds.init),
+				async.apply(meta.blacklist.load)
 			], next);
 		},
 		function(results, next) {
diff --git a/tests/messaging.js b/tests/messaging.js
index ae1db0952a..4758fde40b 100644
--- a/tests/messaging.js
+++ b/tests/messaging.js
@@ -27,39 +27,39 @@ describe('Messaging Library', function() {
 
 	describe('.canMessage()', function() {
 		it('should not error out', function(done) {
-			Messaging.canMessageUser(testUids[1], testUids[2], function(err, allowed) {
+			Messaging.canMessageUser(testUids[1], testUids[2], function(err) {
 				assert.ifError(err);
 				done();
 			});
 		});
 
 		it('should allow messages to be sent to an unrestricted user', function(done) {
-			Messaging.canMessageUser(testUids[1], testUids[2], function(err, allowed) {
-				assert.strictEqual(allowed, true, 'should be true, received ' + allowed);
+			Messaging.canMessageUser(testUids[1], testUids[2], function(err) {
+				assert.ifError(err);
 				done();
 			});
 		});
 
 		it('should NOT allow messages to be sent to a restricted user', function(done) {
 			User.setSetting(testUids[1], 'restrictChat', '1', function() {
-				Messaging.canMessageUser(testUids[2], testUids[1], function(err, allowed) {
-					assert.strictEqual(allowed, false, 'should be false, received ' + allowed);
+				Messaging.canMessageUser(testUids[2], testUids[1], function(err) {
+					assert.strictEqual(err.message, '[[error:chat-restricted]]');
 					done();
 				});
 			});
 		});
 
 		it('should always allow admins through', function(done) {
-			Messaging.canMessageUser(testUids[0], testUids[1], function(err, allowed) {
-				assert.strictEqual(allowed, true, 'should be true, received ' + allowed);
+			Messaging.canMessageUser(testUids[0], testUids[1], function(err) {
+				assert.ifError(err);
 				done();
 			});
 		});
 
 		it('should allow messages to be sent to a restricted user if restricted user follows sender', function(done) {
 			User.follow(testUids[1], testUids[2], function() {
-				Messaging.canMessageUser(testUids[2], testUids[1], function(err, allowed) {
-					assert.strictEqual(allowed, true, 'should be true, received ' + allowed);
+				Messaging.canMessageUser(testUids[2], testUids[1], function(err) {
+					assert.ifError(err);
 					done();
 				});
 			});
diff --git a/tests/topics.js b/tests/topics.js
index bf30147573..fe00db7646 100644
--- a/tests/topics.js
+++ b/tests/topics.js
@@ -173,14 +173,14 @@ describe('Topic\'s', function() {
 		});
 
 		it('should delete the topic', function(done) {
-			topics.delete(newTopic.tid, function(err) {
+			topics.delete(newTopic.tid, 1, function(err) {
 				assert.ifError(err);
 				done();
 			});
 		});
 
 		it('should purge the topic', function(done) {
-			topics.purge(newTopic.tid, function(err) {
+			topics.purge(newTopic.tid, 1, function(err) {
 				assert.ifError(err);
 				done();
 			});
diff --git a/tests/user.js b/tests/user.js
index f9423a62aa..570b002808 100644
--- a/tests/user.js
+++ b/tests/user.js
@@ -184,7 +184,7 @@ describe('User', function() {
 		});
 
 		it('should delete a user account', function(done) {
-			User.delete(uid, function(err) {
+			User.delete(1, uid, function(err) {
 				assert.ifError(err);
 				User.existsBySlug('usertodelete', function(err, exists) {
 					assert.ifError(err);