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/app.js b/app.js index da819da310..c75c70f9cf 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,6 +149,7 @@ function start() { meta.reload(); break; case 'js-propagate': + meta.js.target[message.target] = meta.js.target[message.target] || {}; meta.js.target[message.target].cache = message.cache; meta.js.target[message.target].map = message.map; emitter.emit('meta:js.compiled'); diff --git a/loader.js b/loader.js index 20e8335a9a..5676a02bbd 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, @@ -86,11 +85,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 +122,15 @@ Loader.addWorkerEvents = function(worker) { Loader.reload(); break; case 'js-propagate': - Loader.js.cache = message.cache; - Loader.js.map = message.map; + Loader.js.target[message.target] = Loader.js.target[message.target] || {}; + Loader.js.target[message.target].cache = message.cache; + Loader.js.target[message.target].map = message.map; Loader.notifyWorkers({ action: 'js-propagate', cache: message.cache, - map: message.map + map: message.map, + target: message.target }, worker.pid); break; case 'css-propagate': diff --git a/nodebb b/nodebb index 233dbd350f..5f6f2a77c5 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,193 @@ 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) { + 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 +313,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 7f831cbe69..b035c2fbb2 100644 --- a/package.json +++ b/package.json @@ -43,17 +43,17 @@ "mongodb": "~2.1.3", "morgan": "^1.3.2", "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-composer-default": "3.0.7", + "nodebb-plugin-dbsearch": "1.0.0", + "nodebb-plugin-emoji-extended": "1.0.3", "nodebb-plugin-markdown": "4.0.17", - "nodebb-plugin-mentions": "1.0.17", + "nodebb-plugin-mentions": "1.0.18", "nodebb-plugin-soundpack-default": "0.1.6", "nodebb-plugin-spam-be-gone": "0.4.5", - "nodebb-rewards-essentials": "^0.0.8", - "nodebb-theme-lavender": "3.0.8", - "nodebb-theme-persona": "4.0.88", - "nodebb-theme-vanilla": "5.0.53", + "nodebb-rewards-essentials": "0.0.8", + "nodebb-theme-lavender": "3.0.9", + "nodebb-theme-persona": "4.0.93", + "nodebb-theme-vanilla": "5.0.54", "nodebb-widget-essentials": "2.0.7", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", @@ -74,10 +74,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.3", "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", diff --git a/public/language/ar/notifications.json b/public/language/ar/notifications.json index 52b338e7fa..81733db21e 100644 --- a/public/language/ar/notifications.json +++ b/public/language/ar/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "تم التحقق من عنوان البريد الإلكتروني", "email-confirmed-message": "شكرًا على إثبات صحة عنوان بريدك الإلكتروني. صار حسابك مفعلًا بالكامل.", "email-confirm-error-message": "حدث خطأ أثناء التحقق من عنوان بريدك الإلكتروني. ربما رمز التفعيل خاطئ أو انتهت صلاحيته.", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index c23fbe0c12..823de79421 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -3,17 +3,17 @@ "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-search-term": "Грешен текст за търсене", "invalid-pagination-value": "Грешен номер на страница, трябва да бъде между %1 и %2", "username-taken": "Потребителското име е заето", "email-taken": "Е-пощата е заета", @@ -50,8 +50,8 @@ "still-uploading": "Моля, изчакайте качването да приключи.", "file-too-big": "Максималният разрешен размер на файл е %1 КБ – моля, качете по-малък файл", "guest-upload-disabled": "Качването не е разрешено за гости", - "already-favourited": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "already-favourited": "Вече имате отметка към тази публикация", + "already-unfavourited": "Вече сте премахнали отметката си към тази публикация", "cant-ban-other-admins": "Не можете да блокирате другите администратори!", "cant-remove-last-admin": "Вие сте единственият администратор. Добавете друг потребител като администратор, преди да премахнете себе си като администратор", "invalid-image-type": "Грешен тип на изображение. Позволените типове са: %1", @@ -97,5 +97,5 @@ "invite-maximum-met": "Вие сте поканили максимално позволения брой хора (%1 от %2).", "no-session-found": "Не е открита сесия за вход!", "not-in-room": "Потребителят не е в стаята", - "no-users-in-room": "No users in this room" + "no-users-in-room": "Няма потребители в тази стая" } \ No newline at end of file diff --git a/public/language/bg/global.json b/public/language/bg/global.json index a1a940c50a..3236a43698 100644 --- a/public/language/bg/global.json +++ b/public/language/bg/global.json @@ -87,8 +87,8 @@ "map": "Карта", "sessions": "Сесии за вход", "ip_address": "IP адрес", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", - "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "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 92ba6a905f..017239f5a0 100644 --- a/public/language/bg/groups.json +++ b/public/language/bg/groups.json @@ -49,5 +49,5 @@ "membership.leave-group": "Напускане на групата", "membership.reject": "Отхвърляне", "new-group.group_name": "Име на групата:", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "Качване на снимка за показване на групата" } \ No newline at end of file diff --git a/public/language/bg/notifications.json b/public/language/bg/notifications.json index f47c4b8340..4a6228288d 100644 --- a/public/language/bg/notifications.json +++ b/public/language/bg/notifications.json @@ -16,9 +16,9 @@ "upvoted_your_post_in_multiple": "%1 и %2 други гласуваха положително за Ваша публикация в %3.", "moved_your_post": "%1 премести публикацията Ви в %2", "moved_your_topic": "%1 премести %2", - "favourited_your_post_in": "%1 has bookmarked your post in %2.", - "favourited_your_post_in_dual": "%1 and %2 have bookmarked your post in %3.", - "favourited_your_post_in_multiple": "%1 and %2 others have bookmarked your post in %3.", + "favourited_your_post_in": "%1 си запази отметка към Ваша публикация в %2.", + "favourited_your_post_in_dual": "%1 и %2 си запазиха отметки към Ваша публикация в %3.", + "favourited_your_post_in_multiple": "%1 и %2 други си запазиха отметки към Ваша публикация в %3.", "user_flagged_post_in": "%1 докладва Ваша публикация в %2", "user_flagged_post_in_dual": "%1 и %2 докладваха Ваша публикация в %3", "user_flagged_post_in_multiple": "%1 и %2 други докладваха Ваша публикация в %3", @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 и %2 започнаха да Ви следват.", "user_started_following_you_multiple": "%1 и %2 започнаха да Ви следват.", "new_register": "%1 изпрати заявка за регистрация.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "Е-пощата беше потвърдена", "email-confirmed-message": "Благодарим Ви, че потвърдихте е-пощата си. Акаунтът Ви е вече напълно активиран.", "email-confirm-error-message": "Възникна проблем при потвърждаването на е-пощата Ви. Може кодът да е грешен или давността му да е изтекла.", diff --git a/public/language/bg/pages.json b/public/language/bg/pages.json index 3403d7f897..45e2861bdd 100644 --- a/public/language/bg/pages.json +++ b/public/language/bg/pages.json @@ -33,13 +33,13 @@ "account/posts": "Публикации от %1", "account/topics": "Теми, създадени от %1", "account/groups": "Групите на %1", - "account/favourites": "%1's Bookmarked Posts", + "account/favourites": "Отметнатите публикации на %1", "account/settings": "Потребителски настройки", "account/watched": "Теми, следени от %1", "account/upvoted": "Публикации, получили положителен глас от %1", "account/downvoted": "Публикации, получили отрицателен глас от %1", "account/best": "Най-добрите публикации от %1", - "confirm": "Email Confirmed", + "confirm": "Е-пощата е потвърдена", "maintenance.text": "%1 в момента е в профилактика. Моля, върнете се по-късно.", "maintenance.messageIntro": "В допълнение, администраторът е оставил това съобщение:", "throttled.text": "%1 в момента е недостъпен, поради прекомерно натоварване. Моля, върнете се отново по-късно." diff --git a/public/language/bg/topic.json b/public/language/bg/topic.json index b59e34c52b..dbe44a1541 100644 --- a/public/language/bg/topic.json +++ b/public/language/bg/topic.json @@ -65,9 +65,9 @@ "disabled_categories_note": "Изключените категории са засивени", "confirm_move": "Преместване", "confirm_fork": "Разделяне", - "favourite": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "favourite": "Отметка", + "favourites": "Отметки", + "favourites.has_no_favourites": "Все още не сте си запазвали отметки към никакви публикации.", "loading_more_posts": "Зареждане на още публикации", "move_topic": "Преместване на темата", "move_topics": "Преместване на темите", diff --git a/public/language/bg/user.json b/public/language/bg/user.json index 53e2eebda7..ea9e5c7961 100644 --- a/public/language/bg/user.json +++ b/public/language/bg/user.json @@ -22,7 +22,7 @@ "profile": "Профил", "profile_views": "Преглеждания на профила", "reputation": "Репутация", - "favourites": "Bookmarks", + "favourites": "Отметки", "watched": "Наблюдавани", "followers": "Последователи", "following": "Следва", @@ -55,11 +55,11 @@ "password": "Парола", "username_taken_workaround": "Потребителското име, което искате, е заето и затова ние го променихме малко. Вие ще се наричате %1", "password_same_as_username": "Паролата е същата като потребителското Ви име. Моля, изберете друга парола.", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "password_same_as_email": "Паролата е същата като е-пощата Ви. Моля, изберете друга парола.", "upload_picture": "Качване на снимка", "upload_a_picture": "Качване на снимка", "remove_uploaded_picture": "Премахване на качената снимка", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "Качване на снимка за показване", "settings": "Настройки", "show_email": "Да се показва е-пощата ми", "show_fullname": "Да се показва цялото ми име", diff --git a/public/language/bn/notifications.json b/public/language/bn/notifications.json index 591abc3c4d..497492e0db 100644 --- a/public/language/bn/notifications.json +++ b/public/language/bn/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "ইমেইল নিশ্চিত করা হয়েছে", "email-confirmed-message": "আপনার ইমেইল যাচাই করার জন্য আপনাকে ধন্যবাদ। আপনার অ্যাকাউন্টটি এখন সম্পূর্ণরূপে সক্রিয়।", "email-confirm-error-message": "আপনার ইমেল ঠিকানার বৈধতা যাচাইয়ে একটি সমস্যা হয়েছে। সম্ভবত কোডটি ভুল ছিল অথবা কোডের মেয়াদ শেষ হয়ে গিয়েছে।", diff --git a/public/language/cs/notifications.json b/public/language/cs/notifications.json index 4e29676790..0137a973e3 100644 --- a/public/language/cs/notifications.json +++ b/public/language/cs/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/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 4041a7fc2b..da51315fd6 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,7 +24,7 @@ "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", "no-category": "Kategorien eksisterer ikke", @@ -49,9 +49,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": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "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 +77,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,8 +94,8 @@ "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", - "no-users-in-room": "No users in this 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" } \ No newline at end of file diff --git a/public/language/da/global.json b/public/language/da/global.json index ef47f75ec9..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,10 +85,10 @@ "unfollow": "Følg ikke længere", "delete_all": "Slet alt", "map": "Kort", - "sessions": "Login Sessions", - "ip_address": "IP Address", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", + "sessions": "Login Sessioner", + "ip_address": "IP-adresse", + "enter_page_number": "Indsæt sideantal", + "upload_file": "Upload fil", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "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 6e97812359..4e6340ab6e 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", @@ -49,5 +49,5 @@ "membership.leave-group": "Forlad Gruppe", "membership.reject": "Afvis", "new-group.group_name": "Gruppe Navn:", - "upload-group-cover": "Upload group cover" + "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..e8e62c7d66 100644 --- a/public/language/da/modules.json +++ b/public/language/da/modules.json @@ -7,7 +7,7 @@ "chat.user_has_messaged_you": "1% har skrevet til dig.", "chat.see_all": "Se alle chats", "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 +16,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 +31,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 7a7d49655f..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 %1", "upvoted_your_post_in": "%1 har upvotet dit indlæg i %2.", - "upvoted_your_post_in_dual": "%1 and %2 have upvoted your post in %3.", - "upvoted_your_post_in_multiple": "%1 and %2 others have upvoted your post in %3.", + "upvoted_your_post_in_dual": "%1 og %2 har syntes godt om dit indlæg i %3.", + "upvoted_your_post_in_multiple": "%1 og %2 andre har syntes godt om dit indlæg i%3.", "moved_your_post": "%1 har flyttet dit indlæg til %2", "moved_your_topic": "%1 har flyttet %2", - "favourited_your_post_in": "%1 has bookmarked your post in %2.", - "favourited_your_post_in_dual": "%1 and %2 have bookmarked your post in %3.", - "favourited_your_post_in_multiple": "%1 and %2 others have bookmarked your post in %3.", + "favourited_your_post_in": "%1 har bogmærket dit indlæg i %2.", + "favourited_your_post_in_dual": "%1 og %2 har bogmærket dit indlæg i %3.", + "favourited_your_post_in_multiple": "%1 og %2 andre har bogmærket dit indlæg i %3.", "user_flagged_post_in": "%1 har anmeldt et indlæg i %2", - "user_flagged_post_in_dual": "%1 and %2 flagged a post in %3", - "user_flagged_post_in_multiple": "%1 and %2 others flagged a post in %3", + "user_flagged_post_in_dual": "%1 og %2 har anmeldt et indlæg i %3", + "user_flagged_post_in_multiple": "%1 og %2 andre har anmeldt et indlæg i %3", "user_posted_to": "%1 har skrevet et svar til: %2", - "user_posted_to_dual": "%1 and %2 have posted replies to: %3", - "user_posted_to_multiple": "%1 and %2 others have posted replies to: %3", + "user_posted_to_dual": "%1 og %2 har skrevet svar til: %3", + "user_posted_to_multiple": "%1 og %2 andre har skrevet svar til: %3", "user_posted_topic": "%1 har oprettet en ny tråd: %2", "user_started_following_you": "%1 har valgt at følge dig.", - "user_started_following_you_dual": "%1 and %2 started following you.", - "user_started_following_you_multiple": "%1 and %2 others started following you.", + "user_started_following_you_dual": "%1 og %2 har valgt at følge dig.", + "user_started_following_you_multiple": "%1 og %2 har valgt at følge dig.", "new_register": "%1 har sendt en registrerings anmodning.", + "new_register_multiple": "There are %1 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 076ff501d2..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,13 +33,13 @@ "account/posts": "Indlæg oprettet af %1", "account/topics": "Tråde lavet af %1", "account/groups": "%1s grupper", - "account/favourites": "%1's Bookmarked Posts", + "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", - "confirm": "Email Confirmed", + "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 a19e01b272..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": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "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/user.json b/public/language/da/user.json index 04f90e9066..1488fbf056 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": "Bookmarks", + "favourites": "Bogmærker", "watched": "Set", "followers": "Followers", "following": "Følger", @@ -55,11 +55,11 @@ "password": "Kodeord", "username_taken_workaround": "Det valgte brugernavn er allerede taget, så vi har ændret det en smule. Du hedder nu %1", "password_same_as_username": "Din adgangskode er det samme som dit brugernavn, vælg venligst en anden adgangskode.", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "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", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "Upload coverbillede", "settings": "Indstillinger", "show_email": "Vis min emailaddresse", "show_fullname": "Vis mit fulde navn", @@ -78,9 +78,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", @@ -99,9 +99,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..d96a0b7b38 100644 --- a/public/language/de/email.json +++ b/public/language/de/email.json @@ -14,8 +14,8 @@ "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.cta": "Klicke hier, um %1 zu besuchen", diff --git a/public/language/de/error.json b/public/language/de/error.json index 6f053540f2..5e8e785fd6 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.", @@ -50,8 +50,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": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "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", @@ -83,7 +83,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,6 +96,6 @@ "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", - "no-users-in-room": "No users in this room" + "not-in-room": "Benutzer nicht in Raum", + "no-users-in-room": "In diesem Raum befinden sich keine Benutzer." } \ No newline at end of file diff --git a/public/language/de/global.json b/public/language/de/global.json index b46bf4fcbe..82179436b4 100644 --- a/public/language/de/global.json +++ b/public/language/de/global.json @@ -87,8 +87,8 @@ "map": "Karte", "sessions": "Login-Sitzungen", "ip_address": "IP-Adresse", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", - "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "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 db26671315..18d0489284 100644 --- a/public/language/de/groups.json +++ b/public/language/de/groups.json @@ -49,5 +49,5 @@ "membership.leave-group": "Gruppe verlassen", "membership.reject": "Ablehnen", "new-group.group_name": "Gruppenname:", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "Gruppentitelbild hochladen" } \ No newline at end of file diff --git a/public/language/de/notifications.json b/public/language/de/notifications.json index 438d6abadc..faaeb28215 100644 --- a/public/language/de/notifications.json +++ b/public/language/de/notifications.json @@ -16,9 +16,9 @@ "upvoted_your_post_in_multiple": "%1 und %2 andere Nutzer haben deinen Beitrag in %3 positiv bewertet.", "moved_your_post": "%1 hat deinen Beitrag nach %2 verschoben.", "moved_your_topic": "%1 hat %2 verschoben.", - "favourited_your_post_in": "%1 has bookmarked your post in %2.", - "favourited_your_post_in_dual": "%1 and %2 have bookmarked your post in %3.", - "favourited_your_post_in_multiple": "%1 and %2 others have bookmarked your post in %3.", + "favourited_your_post_in": "%1 hat deinen Beitrag in %2 als Lesezeichen gespeichert.", + "favourited_your_post_in_dual": "%1 und %2 haben deinen Beitrag in %3 als Lesezeichen gespeichert.", + "favourited_your_post_in_multiple": "%1 und %2 andere Nutzer haben deinen Beitrag in %3 als Lesezeichen gespeichert.", "user_flagged_post_in": "%1 hat einen Beitrag in %2 gemeldet", "user_flagged_post_in_dual": "%1 und %2 haben einen Beitrag in %3 gemeldet", "user_flagged_post_in_multiple": "%1 und %2 andere Nutzer haben einen Beitrag in %3 gemeldet", @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 und %2 folgen dir jetzt.", "user_started_following_you_multiple": "%1 und %2 andere Nutzer folgen dir jetzt.", "new_register": "%1 hat eine Registrationsanfrage geschickt.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "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 0844e4d18d..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,15 +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": "%1's Bookmarked Posts", + "account/favourites": "Lesezeichen von %1", "account/settings": "Benutzer-Einstellungen", "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": "Email Confirmed", + "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 7ab5299cc8..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": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "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/user.json b/public/language/de/user.json index 3efc474489..e2cf854439 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -22,7 +22,7 @@ "profile": "Profil", "profile_views": "Profilaufrufe", "reputation": "Ansehen", - "favourites": "Bookmarks", + "favourites": "Lesezeichen", "watched": "Beobachtet", "followers": "Follower", "following": "Folge ich", @@ -55,11 +55,11 @@ "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 %1 bekannt.", "password_same_as_username": "Dein Passwort entspricht deinem Benutzernamen, bitte wähle ein anderes Passwort.", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "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", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "Titelbild hochladen", "settings": "Einstellungen", "show_email": "Zeige meine E-Mail Adresse an.", "show_fullname": "Zeige meinen kompletten Namen an", 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/notifications.json b/public/language/el/notifications.json index eefd1ccc03..53cc1e7fce 100644 --- a/public/language/el/notifications.json +++ b/public/language/el/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "Το Εmail Επιβεβαιώθηκε", "email-confirmed-message": "Ευχαριστούμε που επιβεβαίωσες το email σου. Ο λογαριασμός σου είναι πλέον πλήρως ενεργοποιημένος.", "email-confirm-error-message": "Υπήρξε κάποιο πρόβλημα με την επιβεβαίωση της διεύθυνσής email σου. Ίσως ο κώδικας να είναι άκυρος ή να έχει λήξει.", diff --git a/public/language/en@pirate/notifications.json b/public/language/en@pirate/notifications.json index b22456f660..cee3aa994b 100644 --- a/public/language/en@pirate/notifications.json +++ b/public/language/en@pirate/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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_GB/error.json b/public/language/en_GB/error.json index 2921a55a35..b60c8a2cde 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -125,5 +125,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/groups.json b/public/language/en_GB/groups.json index 5bade84eea..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", 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": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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_US/notifications.json b/public/language/en_US/notifications.json index 0b1d9931a5..28d35e65fa 100644 --- a/public/language/en_US/notifications.json +++ b/public/language/en_US/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/es/notifications.json b/public/language/es/notifications.json index 3bee47fd1a..78dae1fc51 100644 --- a/public/language/es/notifications.json +++ b/public/language/es/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 y %2 comenzaron a seguirte.", "user_started_following_you_multiple": "%1 y otras %2 personas comenzaron a seguirte.", "new_register": "%1 envió una solicitud de registro.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "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/et/notifications.json b/public/language/et/notifications.json index d4d28fb48a..11e2823a75 100644 --- a/public/language/et/notifications.json +++ b/public/language/et/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 ja %2 hakkasid sind jälgima.", "user_started_following_you_multiple": "%1 ja %2 hakkasid sind jälgima.", "new_register": "%1 saatis registreerimistaotluse.", + "new_register_multiple": "There are %1 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/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 a038b8fe03..cce6eb95dd 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -24,7 +24,7 @@ "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 ثانیه پیش از فرستادن پست نخست خود صبر کنید", "no-category": "دسته بندی وجود ندارد", @@ -97,5 +97,5 @@ "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", - "no-users-in-room": "No users in this room" + "no-users-in-room": "هیچ کاربری در این گفتگو نیست" } \ No newline at end of file diff --git a/public/language/fa_IR/groups.json b/public/language/fa_IR/groups.json index 9f4805e44a..9de831f470 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": "مدیر گروه", @@ -49,5 +49,5 @@ "membership.leave-group": "خروج از گروه", "membership.reject": "رد", "new-group.group_name": "نام گروه", - "upload-group-cover": "Upload group cover" + "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..6d9d92806b 100644 --- a/public/language/fa_IR/modules.json +++ b/public/language/fa_IR/modules.json @@ -7,7 +7,7 @@ "chat.user_has_messaged_you": "%1 به شما پیام داده است.", "chat.see_all": "دیدن همه ی چت ها", "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 ec7c0dfd54..ee4641d006 100644 --- a/public/language/fa_IR/notifications.json +++ b/public/language/fa_IR/notifications.json @@ -27,9 +27,10 @@ "user_posted_to_multiple": "%1 and %2 others have posted replies to: %3", "user_posted_topic": "%1 یک موضوع جدید ارسال کرده: %2", "user_started_following_you": "%1 شروع به دنبال کردن شما کرده", - "user_started_following_you_dual": "%1 and %2 started following you.", + "user_started_following_you_dual": "%1 و %2 شروع به دنبال کردن شما کرده.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 یک درخواست ثبت نام ارسال کرده است", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "ایمیل تایید شد", "email-confirmed-message": "بابت تایید ایمیلتان سپاس‌گزاریم. حساب کاربری شما اکنون به صورت کامل فعال شده است.", "email-confirm-error-message": "خطایی در تایید آدرس ایمیل شما پیش آمده است. ممکن است کد نا‌معتبر و یا منقضی شده باشد.", diff --git a/public/language/fi/notifications.json b/public/language/fi/notifications.json index 2c7ee53a9b..68fa97b695 100644 --- a/public/language/fi/notifications.json +++ b/public/language/fi/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/fr/notifications.json b/public/language/fr/notifications.json index 5a24d4c9eb..7ce8c30d06 100644 --- a/public/language/fr/notifications.json +++ b/public/language/fr/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 et %2 vous suivent.", "user_started_following_you_multiple": "%1 et %2 autres vous suivent.", "new_register": "%1 a envoyé une demande d'incription.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "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/gl/notifications.json b/public/language/gl/notifications.json index 34829191b3..2796d782a0 100644 --- a/public/language/gl/notifications.json +++ b/public/language/gl/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 e %2 comezaron a seguirte.", "user_started_following_you_multiple": "%1 e %2 máis comezaron a seguirte.", "new_register": "%1 enviou unha petición de rexistro.", + "new_register_multiple": "There are %1 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/he/error.json b/public/language/he/error.json index d4a969d21a..a5edc06abe 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -50,8 +50,8 @@ "still-uploading": "אנא המתן לסיום ההעלאות", "file-too-big": "הגודל המקסימלי של הקובץ הוא %1 קילובייט - אנא העלה קובץ קטן יותר", "guest-upload-disabled": "העלאת אורחים אינה מאופשרת", - "already-favourited": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "already-favourited": "כבר סימנת את הפוסט הזה", + "already-unfavourited": "כבר הסרת את הסימון מפוסט זה", "cant-ban-other-admins": "אינך יכול לחסום מנהלים אחרים!", "cant-remove-last-admin": "אתה המנהל היחיד. הוסף משתמש אחר לניהול לפני שאתה מוריד את עצמך מניהול", "invalid-image-type": "פורמט תמונה לא תקין. הפורמטים המורשים הם: %1", @@ -97,5 +97,5 @@ "invite-maximum-met": "הזמנת את הכמות המירבית של אנשים (%1 מתוך %2).", "no-session-found": "לא נמצאו סשני התחברות!", "not-in-room": "משתמש זה לא בצ'אט", - "no-users-in-room": "No users in this room" + "no-users-in-room": "אין משתמש בחדר הזה" } \ No newline at end of file diff --git a/public/language/he/global.json b/public/language/he/global.json index b1ada66688..f4b966429e 100644 --- a/public/language/he/global.json +++ b/public/language/he/global.json @@ -87,8 +87,8 @@ "map": "מפה", "sessions": "סשני התחברות", "ip_address": "כתובת IP", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", - "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "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 30ffe57467..abd3ef14d8 100644 --- a/public/language/he/groups.json +++ b/public/language/he/groups.json @@ -49,5 +49,5 @@ "membership.leave-group": "עזוב קבוצה", "membership.reject": "דחה", "new-group.group_name": "שם קבוצה", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "העלה תמונת נושא לקבוצה" } \ No newline at end of file diff --git a/public/language/he/notifications.json b/public/language/he/notifications.json index d6cddc6d7e..30255302fc 100644 --- a/public/language/he/notifications.json +++ b/public/language/he/notifications.json @@ -16,9 +16,9 @@ "upvoted_your_post_in_multiple": "%1 ו%2 אחרים הצביעו לפוסט שלך ב%3.", "moved_your_post": "%1 העביר את הפוסט שלך ל%2", "moved_your_topic": "%1 הוזז ל%2", - "favourited_your_post_in": "%1 has bookmarked your post in %2.", - "favourited_your_post_in_dual": "%1 and %2 have bookmarked your post in %3.", - "favourited_your_post_in_multiple": "%1 and %2 others have bookmarked your post in %3.", + "favourited_your_post_in": "%1 סימן את הפוסט שלך ב%2.", + "favourited_your_post_in_dual": "%1 ו%2 סימנו את הפוסט שלך ב%3.", + "favourited_your_post_in_multiple": "%1 ו-%2 אחרים סימנו את הפוסט שלך ב%3.", "user_flagged_post_in": "%1 דיווח על פוסט ב %2", "user_flagged_post_in_dual": "%1 ו%2 סימנו פוסט ב%3", "user_flagged_post_in_multiple": "%1 ו%2 נוספים סימנו פוסט ב%3", @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 ו%1 התחילו לעקוב אחריך.", "user_started_following_you_multiple": "%1 ו%2 התחילו לעקוב אחריך.", "new_register": "%1 שלח בקשת הרשמה.", + "new_register_multiple": "There are %1 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 de5b360411..0a36793f6a 100644 --- a/public/language/he/pages.json +++ b/public/language/he/pages.json @@ -33,13 +33,13 @@ "account/posts": "הודעות שפורסמו על ידי %1", "account/topics": "נושאים שנוצרו על ידי %1", "account/groups": "הקבוצות של %1", - "account/favourites": "%1's Bookmarked Posts", + "account/favourites": "הפוסטים שסומנו על ידי %1", "account/settings": "הגדרות משתמש", "account/watched": "נושאים שנצפו על ידי %1", "account/upvoted": "פוסטים שהוצבעו לטובה על ידי %1", "account/downvoted": "פוסטים שהוצבעו לרעה על ידי %1", "account/best": "הפוסטים הטובים ביותר שנוצרו על ידי %1", - "confirm": "Email Confirmed", + "confirm": "כתובת המייל אושרה", "maintenance.text": "%1 כרגע תחת עבודות תחזוקה. אנא חזור בזמן מאוחר יותר.", "maintenance.messageIntro": "בנוסף, המנהל השאיר את ההודעה הזו:", "throttled.text": "%1 לא זמן כעת עקב טעינת יתר. אנא חזור מאוחר יותר." diff --git a/public/language/he/topic.json b/public/language/he/topic.json index 84200d1908..49ca0e5bba 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -65,9 +65,9 @@ "disabled_categories_note": "קטגוריות מבוטלות צבועות באפור", "confirm_move": "הזז", "confirm_fork": "שכפל", - "favourite": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "favourite": "סימניה", + "favourites": "סימניות", + "favourites.has_no_favourites": "עוד לא סימנת שום פוסט.", "loading_more_posts": "טוען פוסטים נוספים", "move_topic": "הזז נושא", "move_topics": "הזז נושאים", diff --git a/public/language/he/user.json b/public/language/he/user.json index ea8a1aaffd..e830f21f58 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -22,7 +22,7 @@ "profile": "פרופיל", "profile_views": "צפיות בפרופיל", "reputation": "מוניטין", - "favourites": "Bookmarks", + "favourites": "סימניות", "watched": "נצפה", "followers": "עוקבים", "following": "עוקב אחרי", @@ -55,11 +55,11 @@ "password": "סיסמה", "username_taken_workaround": "שם המשתמש שבחרת כבר תפוס, אז שינינו אותו מעט. שם המשתמש שלך כעת הוא %1", "password_same_as_username": "הסיסמה שלך זהה לשם המשתמש, אנא בחר סיסמה שונה.", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "password_same_as_email": "הסיסמה שלך זהה לכתובת המייל שלך, אנא בחר סיסמה שונה.", "upload_picture": "העלה תמונה", "upload_a_picture": "העלה תמונה", "remove_uploaded_picture": "מחק את התמונה שהועלתה", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "העלה תמונת נושא", "settings": "הגדרות", "show_email": "פרסם את כתובת האימייל שלי", "show_fullname": "הצג את שמי המלא", diff --git a/public/language/hu/notifications.json b/public/language/hu/notifications.json index 33f71e2c35..b1b09a5dd7 100644 --- a/public/language/hu/notifications.json +++ b/public/language/hu/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/id/notifications.json b/public/language/id/notifications.json index aeb0f894a7..e4290a117d 100644 --- a/public/language/id/notifications.json +++ b/public/language/id/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 mengirim permintaan registrasi.", + "new_register_multiple": "There are %1 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/it/notifications.json b/public/language/it/notifications.json index 3addf77765..fc8f3bccc8 100644 --- a/public/language/it/notifications.json +++ b/public/language/it/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 ha inviato una richiesta di registrazione.", + "new_register_multiple": "There are %1 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/ja/notifications.json b/public/language/ja/notifications.json index 9d0ca95f07..5fff05542b 100644 --- a/public/language/ja/notifications.json +++ b/public/language/ja/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/ko/error.json b/public/language/ko/error.json index 0bd26d9348..e921dba067 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -50,8 +50,8 @@ "still-uploading": "업로드가 끝날 때까지 기다려주세요.", "file-too-big": "업로드 가능한 파일크기는 최대 %1 KB 입니다 - 파일의 용량을 줄이거나 압축을 활용하세요.", "guest-upload-disabled": "손님의 파일 업로드는 제한되어 있습니다.", - "already-favourited": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "already-favourited": "이미 이 게시물을 북마크 했습니다.", + "already-unfavourited": "이미 이 게시물을 북마크 해제했습니다.", "cant-ban-other-admins": "다른 관리자를 차단할 수 없습니다.", "cant-remove-last-admin": "귀하는 유일한 관리자입니다. 관리자를 그만두시기 전에 다른 사용자를 관리자로 선임하세요.", "invalid-image-type": "올바르지 않은 이미지입니다. 사용가능한 유형: %1", @@ -96,6 +96,6 @@ "wrong-login-type-username": "사용자명을 통해 로그인하세요.", "invite-maximum-met": "초대가능한 사용자를 모두 초대했습니다. (%2명 중 %1을 초대)", "no-session-found": "로그인 세션을 찾을 수 없습니다.", - "not-in-room": "없는 유저입니다.", - "no-users-in-room": "No users in this room" + "not-in-room": "없는 사용자입니다.", + "no-users-in-room": "사용자가 없습니다." } \ No newline at end of file diff --git a/public/language/ko/global.json b/public/language/ko/global.json index 4457d09311..9669750309 100644 --- a/public/language/ko/global.json +++ b/public/language/ko/global.json @@ -51,7 +51,7 @@ "posts": "게시물", "best": "베스트", "upvoted": "Upvoted", - "downvoted": "Downvoted", + "downvoted": "비추됨", "views": "조회 수", "reputation": "인기도", "read_more": "전체 보기", @@ -87,8 +87,8 @@ "map": "맵", "sessions": "로그인 세션", "ip_address": "아이피 주소", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", - "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "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 bea6c7d39c..e1f5fa65d0 100644 --- a/public/language/ko/groups.json +++ b/public/language/ko/groups.json @@ -49,5 +49,5 @@ "membership.leave-group": "그룹 나가기", "membership.reject": "거절", "new-group.group_name": "그룹명:", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "그룹 커버 업로드" } \ No newline at end of file diff --git a/public/language/ko/notifications.json b/public/language/ko/notifications.json index 910681d2fb..40a4b6af0b 100644 --- a/public/language/ko/notifications.json +++ b/public/language/ko/notifications.json @@ -13,12 +13,12 @@ "new_message_from": "%1님이 메시지를 보냈습니다.", "upvoted_your_post_in": "%1님이 %2의 내 게시물을 추천했습니다.", "upvoted_your_post_in_dual": "%1님과 %2님이 %3의 내 게시물을 추천했습니다.", - "upvoted_your_post_in_multiple": "%1 님과 다른 %2 명이 %3 안의 당신의 게시물을 upvote 했습니다.", + "upvoted_your_post_in_multiple": "%1 님과 다른 %2 명이 %3의 내 게시물을 추천했습니다.", "moved_your_post": "%1님이 귀하의 게시물을 %2로 옮겼습니다.", "moved_your_topic": "%1%2 로 옮겨졌습니다.", - "favourited_your_post_in": "%1 has bookmarked your post in %2.", - "favourited_your_post_in_dual": "%1 and %2 have bookmarked your post in %3.", - "favourited_your_post_in_multiple": "%1 and %2 others have bookmarked your post in %3.", + "favourited_your_post_in": "%1님이 %2의 내 게시물을 북마크 했습니다.", + "favourited_your_post_in_dual": "%1 님과 %2 님이 %3의 내 게시물을 북마크 했습니다.", + "favourited_your_post_in_multiple": "%1 님과 다른 %2 명이 %3의 내 게시물을 북마크 했습니다.", "user_flagged_post_in": "%1님이 %2의 게시물을 신고했습니다.", "user_flagged_post_in_dual": "%1 님과 %2 님이 %3 안의 게시물에 플래그를 세웠습니다.", "user_flagged_post_in_multiple": "%1 님과 %2 명의 다른 유저들이 %3 안의 게시물에 플래그를 세웠습니다.", @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1님과 %2님이 당신을 팔로우 시작했습니다.", "user_started_following_you_multiple": "%1님외 %2명이 당신을 팔로우 시작했습니다.", "new_register": "%1님이 가입요청을 했습니다.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "확인된 이메일", "email-confirmed-message": "이메일을 확인해주셔서 감사합니다. 계정이 완전히 활성화되었습니다.", "email-confirm-error-message": "이메일 주소를 검증하지 못했습니다. 코드가 올바르지 않거나 만료되었을 수 있습니다.", diff --git a/public/language/ko/pages.json b/public/language/ko/pages.json index 242c610438..65130b5b29 100644 --- a/public/language/ko/pages.json +++ b/public/language/ko/pages.json @@ -33,13 +33,13 @@ "account/posts": "%1 님이 작성한 게시물", "account/topics": "%1 님이 생성한 주제", "account/groups": "%1님의 그룹", - "account/favourites": "%1's Bookmarked Posts", + "account/favourites": "%1님의 북마크된 게시물", "account/settings": "사용자 설정", "account/watched": "%1님이 지켜보는 주제", "account/upvoted": "%1 님이 upvote한 게시물", "account/downvoted": "%1 님에 의해 Downvote된 게시물", "account/best": "%1 님 최고의 게시물", - "confirm": "Email Confirmed", + "confirm": "확인된 이메일", "maintenance.text": "%1 사이트는 현재 점검 중입니다. 나중에 다시 방문해주세요.", "maintenance.messageIntro": "다음은 관리자가 전하는 메시지입니다.", "throttled.text": "과도한 부하로 %1 를 로드할 수 없습니다. 잠시후에 다시 시도해주세요." diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index 59b1ae0e47..056ccb5a8f 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -65,9 +65,9 @@ "disabled_categories_note": "비활성화된 카테고리는 회색으로 표시됩니다.", "confirm_move": "이동", "confirm_fork": "분리", - "favourite": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "favourite": "북마크", + "favourites": "북마크", + "favourites.has_no_favourites": "북마크한 게시글이 없습니다.", "loading_more_posts": "게시물을 로딩 중", "move_topic": "주제 이동", "move_topics": "주제 이동", diff --git a/public/language/ko/user.json b/public/language/ko/user.json index 592af0f1ce..afb32ac984 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -22,7 +22,7 @@ "profile": "프로필", "profile_views": "프로필 조회 수", "reputation": "인기도", - "favourites": "Bookmarks", + "favourites": "북마크", "watched": "읽음", "followers": "이 사용자를 팔로우", "following": "이 사용자가 팔로우", @@ -43,23 +43,23 @@ "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": "새 사용자 이름이 이미 존재하여 %1로 저장되었습니다.", - "password_same_as_username": "패스워드가 사용자명과 동일합니다. 다시 입력하세요.", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "password_same_as_username": "비밀번호가 사용자명과 동일합니다. 다른 비밀번호를 입력하세요.", + "password_same_as_email": "비밀번호가 이메일 주소와 동일합니다. 다른 비밀번호를 입력하세요.", "upload_picture": "사진 업로드", "upload_a_picture": "사진 업로드", "remove_uploaded_picture": "등록된 사진을 삭제", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "커버 사진 업로드", "settings": "설정", "show_email": "이메일 공개", "show_fullname": "실명 공개", diff --git a/public/language/lt/notifications.json b/public/language/lt/notifications.json index 079591f953..640f9bde08 100644 --- a/public/language/lt/notifications.json +++ b/public/language/lt/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 atsiuntė registracijos prašymą", + "new_register_multiple": "There are %1 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/ms/notifications.json b/public/language/ms/notifications.json index 2700d7d248..dab972b82d 100644 --- a/public/language/ms/notifications.json +++ b/public/language/ms/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 dan %2 mula mengikuti anda.", "user_started_following_you_multiple": "%1 dan %2 lagi mula mengikuti anda.", "new_register": "%1 menghantar jemputan pendaftaran.", + "new_register_multiple": "There are %1 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/nb/notifications.json b/public/language/nb/notifications.json index 927f144414..f532425c00 100644 --- a/public/language/nb/notifications.json +++ b/public/language/nb/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sendte en forespørsel om registrering", + "new_register_multiple": "There are %1 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/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": "Er zijn geen onderwerpen in deze categorie.
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/error.json b/public/language/nl/error.json index 936903cf0f..b23072f895 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -1,30 +1,30 @@ { "invalid-data": "Ongeldige Data", - "not-logged-in": "Dit account lijkt op dit moment niet ingelogd te zijn.", + "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-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-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.", "no-category": "Categorie bestaat niet", @@ -50,8 +50,8 @@ "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": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "already-favourited": "Je hebt dit bericht al als favoriet toegevoegd", + "already-unfavourited": "Je hebt dit bericht al verwijderd uit de 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", "invalid-image-type": "Ongeldig bestandstype afbeelding. Deze afbeelding is van een bestandstype dat niet ondersteund wordt. Toegestane bestandstypes voor afbeeldingsbestanden zijn: %1", @@ -97,5 +97,5 @@ "invite-maximum-met": "Je heb het maximum aantal mensen uitgenodigd (%1 van de %2).", "no-session-found": "Geen login sessie gevonden!", "not-in-room": "Geen gebruiker in deze chat room", - "no-users-in-room": "No users in this room" + "no-users-in-room": "Er zijn geen gebruikers in deze chat" } \ No newline at end of file diff --git a/public/language/nl/global.json b/public/language/nl/global.json index 3a3d7afe18..65ff6ff8f4 100644 --- a/public/language/nl/global.json +++ b/public/language/nl/global.json @@ -2,7 +2,7 @@ "home": "Home", "search": "Zoeken", "buttons.close": "Sluiten", - "403.title": "Geen toegang", + "403.title": "Toegang Geweigerd", "403.message": "Deze account heeft onvoldoende systeemrechten om toegang tot de pagina te krijgen.", "403.login": "Wellicht proberen aan te melden?", "404.title": "Niet gevonden", @@ -87,8 +87,8 @@ "map": "Kaart", "sessions": "Login Sessies", "ip_address": "IP Adres", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", + "enter_page_number": "Voer paginanummer in", + "upload_file": "Upload bestand", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "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 82100e579f..72702a3fe4 100644 --- a/public/language/nl/groups.json +++ b/public/language/nl/groups.json @@ -1,8 +1,8 @@ { "groups": "Groepen", - "view_group": "Weergeven groep", + "view_group": "Bekijk Groep", "owner": "Groepseigenaar", - "new_group": "Nieuwe groep", + "new_group": "Nieuwe groep aanmaken", "no_groups_found": "Geen groepen voor weergave", "pending.accept": "Accepteer", "pending.reject": "Afwijzen", @@ -49,5 +49,5 @@ "membership.leave-group": "Verlaat groep", "membership.reject": "Afwijzen", "new-group.group_name": "Groepsnaam:", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "Upload groepscover" } \ No newline at end of file diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json index ba06d7ce93..984c39b4be 100644 --- a/public/language/nl/notifications.json +++ b/public/language/nl/notifications.json @@ -16,9 +16,9 @@ "upvoted_your_post_in_multiple": "%1 en %2 andere hebben in gestemd in %3.", "moved_your_post": "%1 heeft je bericht verplaatst naar %2", "moved_your_topic": "%1 heeft %2 verplaatst", - "favourited_your_post_in": "%1 has bookmarked your post in %2.", - "favourited_your_post_in_dual": "%1 and %2 have bookmarked your post in %3.", - "favourited_your_post_in_multiple": "%1 and %2 others have bookmarked your post in %3.", + "favourited_your_post_in": "%1 heeft je bericht in %2 aan zijn/haar favorieten toegevoegd.", + "favourited_your_post_in_dual": "%1 en %2 hebben je bericht in %3 aan hun favorieten toegevoegd.", + "favourited_your_post_in_multiple": "%1 en %2 anderen hebben je bericht in %3 aan hun favorieten toegevoegd.", "user_flagged_post_in": "%1 rapporteerde een bericht in %2", "user_flagged_post_in_dual": "%1 en %2 rapporteerde een bericht in %3", "user_flagged_post_in_multiple": "%1 en %2 andere rapporteede een bericht in %3", @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 en %2 volgen jou nu.", "user_started_following_you_multiple": "%1 en %2 andere volgen jou nu.", "new_register": "%1 heeft een registratie verzoek aangevraagd.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "E-mailadres bevestigd", "email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Dit 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.", diff --git a/public/language/nl/pages.json b/public/language/nl/pages.json index 131dccb6e4..96f5f11a38 100644 --- a/public/language/nl/pages.json +++ b/public/language/nl/pages.json @@ -1,7 +1,7 @@ { "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", @@ -33,13 +33,13 @@ "account/posts": "Berichten geplaatst door %1", "account/topics": "Onderwerpen begonnen door %1", "account/groups": "%1's groepen", - "account/favourites": "%1's Bookmarked Posts", + "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 Confirmed", + "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/reset_password.json b/public/language/nl/reset_password.json index 9849338c56..a156d0bf1c 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": "

Wachtwoord met succes hersteld. Log nu eerst opnieuw in.", + "password_changed.message": "

Wachtwoord is met succes hersteld. Opnieuw inloggen.", "wrong_reset_code.title": "Onjuiste herstelcode", "wrong_reset_code.message": "Opgegeven code voor wachtwoordherstel is niet juist. Probeer het opnieuw of vraag een andere code aan.", "new_password": "Nieuw wachtwoord", diff --git a/public/language/nl/search.json b/public/language/nl/search.json index da9e793e8b..5e5e7224f0 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": "Geavanceerde 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..e418f7e610 100644 --- a/public/language/nl/success.json +++ b/public/language/nl/success.json @@ -1,6 +1,6 @@ { "success": "Geslaagd", - "topic-post": "Bericht succesvol geplaatst", - "authentication-successful": "Aanmelden geslaagd", + "topic-post": "Je bericht is met succes geplaatst.", + "authentication-successful": "Authenticatie 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 d597851e73..c1bcd0f1ca 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -65,9 +65,9 @@ "disabled_categories_note": "Uitgeschakelde Categorieën zijn grijs", "confirm_move": "Verplaatsen", "confirm_fork": "Splits", - "favourite": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "favourite": "Favoriet", + "favourites": "Favorieten", + "favourites.has_no_favourites": "Je hebt nog geen berichten aan je favorieten toegevoegd.", "loading_more_posts": "Meer berichten...", "move_topic": "Onderwerp verplaatsen", "move_topics": "Verplaats onderwerpen", 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/user.json b/public/language/nl/user.json index 036e69dd6d..d3a5a4546f 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -22,7 +22,7 @@ "profile": "Profiel", "profile_views": "Bekeken", "reputation": "Reputatie", - "favourites": "Bookmarks", + "favourites": "Favorieten", "watched": "Bekeken", "followers": "Volgers", "following": "Volgend", @@ -55,11 +55,11 @@ "password": "Wachtwoord", "username_taken_workaround": "Helaas, de gewenste gebruikersnaam is al door iemand in gebruik genomen dus vandaar een kleine aanpassing naar %1 doorgevoerd", "password_same_as_username": "Je wachtwoord is hetzelfde als je gebruikersnaam. Kies een ander wachtwoord.", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "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", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "Upload je coverafbeelding", "settings": "Instellingen", "show_email": "Inschakelen weergave van e-mailadres op profielpagina", "show_fullname": "Laat mijn volledige naam zien", diff --git a/public/language/nl/users.json b/public/language/nl/users.json index 2330e1b0e6..f282c53f6c 100644 --- a/public/language/nl/users.json +++ b/public/language/nl/users.json @@ -1,5 +1,5 @@ { - "latest_users": "Meest recente gebruikers", + "latest_users": "Recenste Gebruikers", "top_posters": "Meest actieve leden", "most_reputation": "Meeste reputatie", "search": "Zoeken", diff --git a/public/language/pl/notifications.json b/public/language/pl/notifications.json index 4bafa8c8e7..5873b93a47 100644 --- a/public/language/pl/notifications.json +++ b/public/language/pl/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 wysłał żądanie rejestracji.", + "new_register_multiple": "There are %1 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/pt_BR/notifications.json b/public/language/pt_BR/notifications.json index a50d8a3f4f..0f099da10b 100644 --- a/public/language/pt_BR/notifications.json +++ b/public/language/pt_BR/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 e %2 começaram a lhe acompanhar.", "user_started_following_you_multiple": "%1 e %2 outros começaram a lhe acompanhar.", "new_register": "%1 lhe enviou um pedido de cadastro.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "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/ro/notifications.json b/public/language/ro/notifications.json index 1c01843b8a..9684a7d9cc 100644 --- a/public/language/ro/notifications.json +++ b/public/language/ro/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/ru/notifications.json b/public/language/ru/notifications.json index 4a40bdc5c3..21c0a3b46c 100644 --- a/public/language/ru/notifications.json +++ b/public/language/ru/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 и %2 подписались на вас.", "user_started_following_you_multiple": "%1 и %2 подписались на вас.", "new_register": "%1 отправил запрос на регистрацию.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "Email подтвержден", "email-confirmed-message": "Спасибо за подтверждение Вашего Email-адреса. Ваш аккаунт активирован.", "email-confirm-error-message": "Ошибка проверки Email-адреса. Возможно, код неверен, либо у него истек срок действия.", diff --git a/public/language/rw/notifications.json b/public/language/rw/notifications.json index 83a7b7eb4e..60fa3be43d 100644 --- a/public/language/rw/notifications.json +++ b/public/language/rw/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 yasabye kwandikwa.", + "new_register_multiple": "There are %1 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/sc/notifications.json b/public/language/sc/notifications.json index fc3ba602f5..5f7fd03816 100644 --- a/public/language/sc/notifications.json +++ b/public/language/sc/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/sk/notifications.json b/public/language/sk/notifications.json index fbb3535f62..160a4d9e86 100644 --- a/public/language/sk/notifications.json +++ b/public/language/sk/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 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/sl/notifications.json b/public/language/sl/notifications.json index f5d94351e4..37711ea13d 100644 --- a/public/language/sl/notifications.json +++ b/public/language/sl/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 je poslal prošnjo za registracijo.", + "new_register_multiple": "There are %1 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/sr/notifications.json b/public/language/sr/notifications.json index d919bc82e7..52a2b2d949 100644 --- a/public/language/sr/notifications.json +++ b/public/language/sr/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 и %2 су почели да вас прате.", "user_started_following_you_multiple": "%1 и %2 других су почели да вас прате.", "new_register": "%1 вам је послао захтев за регистрацију.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "Е-пошта је је отврђена.", "email-confirmed-message": "Хвала на овери ваше е-поште. Ваш налог је сада у потпуности активан.", "email-confirm-error-message": "Дошло је до проблема са овером ваше е-поште. Можда је код неисправан или истекао.", diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json index 0db2dee665..743451971a 100644 --- a/public/language/sv/notifications.json +++ b/public/language/sv/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 skickade en registreringsförfrågan.", + "new_register_multiple": "There are %1 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/th/notifications.json b/public/language/th/notifications.json index a00e594a93..dd81dfdb7e 100644 --- a/public/language/th/notifications.json +++ b/public/language/th/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "Email ได้รับการยืนยันแล้ว", "email-confirmed-message": "ขอบคุณที่ยืนยัน Email ของคุณ บัญชีของคุณสามารถใช้งานได้แล้ว", "email-confirm-error-message": "มีปัญหาในการยืนยัน Email ของคุณ บางทีรหัสไม่ถูกต้องหรือหมดอายุแล้ว", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index f086d8dcbe..e8796fbdd0 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -50,8 +50,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": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "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", @@ -97,5 +97,5 @@ "invite-maximum-met": "Sen maksimum miktarda insanı davet ettin (%2 üzerinden %1).", "no-session-found": "Giriş yapılmış bir oturum bulunamadı!", "not-in-room": "Odada kullanıcı yok", - "no-users-in-room": "No users in this room" + "no-users-in-room": "Bu odada kullanıcı yok" } \ No newline at end of file diff --git a/public/language/tr/global.json b/public/language/tr/global.json index 72455cc80f..2cf32930e5 100644 --- a/public/language/tr/global.json +++ b/public/language/tr/global.json @@ -87,8 +87,8 @@ "map": "Harita", "sessions": "Giriş Oturumları", "ip_address": "IP Adresleri", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", - "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "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 1618633a48..234f0a36be 100644 --- a/public/language/tr/groups.json +++ b/public/language/tr/groups.json @@ -49,5 +49,5 @@ "membership.leave-group": "Gruptan Ayrıl", "membership.reject": "Reddet", "new-group.group_name": "Grup İsmi:", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "Grup kapağı yükle" } \ No newline at end of file diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json index dfd4849065..eddd9194ea 100644 --- a/public/language/tr/notifications.json +++ b/public/language/tr/notifications.json @@ -16,9 +16,9 @@ "upvoted_your_post_in_multiple": "%1 ve %2 iki kişi daha %3 içindeki gönderini beğendi.", "moved_your_post": "%1 senin iletin %2 taşındı", "moved_your_topic": "%1 taşındı %2", - "favourited_your_post_in": "%1 has bookmarked your post in %2.", - "favourited_your_post_in_dual": "%1 and %2 have bookmarked your post in %3.", - "favourited_your_post_in_multiple": "%1 and %2 others have bookmarked your post in %3.", + "favourited_your_post_in": "%1 %2 içindeki gönderini yer imlerine ekledi.", + "favourited_your_post_in_dual": "%1 ve %2 %3 gönderini yer imlerine ekledi.", + "favourited_your_post_in_multiple": "%1 ve %2 kişi daha %3 gönderini yer imlerine ekledi.", "user_flagged_post_in": "%1 bir iletiyi bayrakladı. %2", "user_flagged_post_in_dual": " %1 ve %2 %3 gönderini bayrakladı", "user_flagged_post_in_multiple": "%1 ve %2 kişi daha %3 gönderini bayrakladı", @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 ve %2 seni takip etmeye başladı.\n", "user_started_following_you_multiple": "%1 ve %2 kişi daha seni takip etmeye başladı.", "new_register": "%1 kayıt olma isteği gönderdi.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "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 77d821ad43..b076bf11d9 100644 --- a/public/language/tr/pages.json +++ b/public/language/tr/pages.json @@ -33,7 +33,7 @@ "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's Bookmarked Posts", + "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", diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index ea140fc028..e12c92f005 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -65,9 +65,9 @@ "disabled_categories_note": "Etkin Olmayan Kategoriler soluklaştırılır", "confirm_move": "Taşı", "confirm_fork": "Ayır", - "favourite": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "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/user.json b/public/language/tr/user.json index e793dd7b0e..2dbac92881 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": "Bookmarks", + "favourites": "Yer imleri", "watched": "İzlendi", "followers": "Takipçiler", "following": "Takip Ediyor", @@ -59,7 +59,7 @@ "upload_picture": "Resim Yükle", "upload_a_picture": "Bir Resim Yükle", "remove_uploaded_picture": "Yüklenmiş fotoğrafı kaldır", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "Kapak fotoğrafı yükle", "settings": "Ayarlar", "show_email": "E-postamı göster", "show_fullname": "Tam ismimi göster", diff --git a/public/language/vi/notifications.json b/public/language/vi/notifications.json index 8780782f08..1520437ea9 100644 --- a/public/language/vi/notifications.json +++ b/public/language/vi/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1%2 đã bắt đầu theo dõi bạn.", "user_started_following_you_multiple": "%1 và %2 người khác đã bắt đầu theo dõi bạn.", "new_register": "%1 đã gửi một yêu cầu tham gia.", + "new_register_multiple": "There are %1 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/zh_CN/groups.json b/public/language/zh_CN/groups.json index 45964d8d2b..ae95b86cd1 100644 --- a/public/language/zh_CN/groups.json +++ b/public/language/zh_CN/groups.json @@ -49,5 +49,5 @@ "membership.leave-group": "退出小组", "membership.reject": "拒绝", "new-group.group_name": "组名: ", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "上传组封面" } \ No newline at end of file diff --git a/public/language/zh_CN/notifications.json b/public/language/zh_CN/notifications.json index 9d22253112..8449a4a853 100644 --- a/public/language/zh_CN/notifications.json +++ b/public/language/zh_CN/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1%2 关注了您。", "user_started_following_you_multiple": "%1 和 %2 个其他人关注了您。", "new_register": "%1 发出了注册请求", + "new_register_multiple": "There are %1 registration requests awaiting review.", "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 bc9f7f1fa5..66e705c543 100644 --- a/public/language/zh_CN/pages.json +++ b/public/language/zh_CN/pages.json @@ -11,7 +11,7 @@ "users/latest": "最新会员", "users/sort-posts": "最多发帖的会员", "users/sort-reputation": "最多积分的会员", - "users/banned": "Banned Users", + "users/banned": "被封禁的用户", "users/search": "会员搜索", "notifications": "提醒", "tags": "话题", @@ -39,7 +39,7 @@ "account/upvoted": "帖子被 %1 顶过", "account/downvoted": "帖子被 %1 踩过", "account/best": "%1 发布的最佳帖子", - "confirm": "Email Confirmed", + "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 7b1a9d98f0..6464255b13 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": "当此主题有新回复时,通知我", diff --git a/public/language/zh_TW/notifications.json b/public/language/zh_TW/notifications.json index 477d2e91ab..3174feb38e 100644 --- a/public/language/zh_TW/notifications.json +++ b/public/language/zh_TW/notifications.json @@ -30,6 +30,7 @@ "user_started_following_you_dual": "%1 and %2 started following you.", "user_started_following_you_multiple": "%1 and %2 others started following you.", "new_register": "%1 sent a registration request.", + "new_register_multiple": "There are %1 registration requests awaiting review.", "email-confirmed": "已確認電郵", "email-confirmed-message": "感謝您驗證您的電郵。您的帳戶現已全面啟用。", "email-confirm-error-message": "驗證您的電郵地址時出現問題。也許啟動碼無效或已過期。", 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 930e996cdc..8595ac3b81 100644 --- a/public/src/admin/general/dashboard.js +++ b/public/src/admin/general/dashboard.js @@ -8,6 +8,7 @@ define('admin/general/dashboard', ['semver'], function(semver) { graphs: false }, isMobile = false, + isPrerelease = /^v?\d+\.\d+\.\d+-.+$/, graphData = { rooms: {}, traffic: {} @@ -22,7 +23,17 @@ define('admin/general/dashboard', ['semver'], function(semver) { 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'], function(semver) { 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('', 'v' + latestVersion)); // Alter box colour accordingly if (semver.eq(latestVersion, version)) { checkEl.removeClass('alert-info').addClass('alert-success'); checkEl.append('

You are up-to-date

'); } else if (semver.gt(latestVersion, version)) { - checkEl.removeClass('alert-info').addClass('alert-danger'); - checkEl.append('

A new version (v' + latestVersion + ') has been released. Consider upgrading your NodeBB.

'); + checkEl.removeClass('alert-info').addClass('alert-warning'); + if (!isPrerelease.test(version)) { + checkEl.append('

A new version (v' + latestVersion + ') has been released. Consider upgrading your NodeBB.

'); + } else { + checkEl.append('

This is an outdated pre-release version of NodeBB. A new version (v' + latestVersion + ') has been released. Consider upgrading your NodeBB.

'); + } + } else if (isPrerelease.test(version)) { + checkEl.removeClass('alert-info').addClass('alert-info'); + checkEl.append('

This is a pre-release version of NodeBB. Unintended bugs may occur. .

'); } }); @@ -158,6 +166,7 @@ define('admin/general/dashboard', ['semver'], function(semver) { if (isMobile) { Chart.defaults.global.showTooltips = false; + Chart.defaults.global.animation = false; } var data = { @@ -186,7 +195,7 @@ define('admin/general/dashboard', ['semver'], function(semver) { ] }; - 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/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/ajaxify.js b/public/src/ajaxify.js index c6b9418693..608e0a6cda 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 @@ -63,7 +64,7 @@ $(document).ready(function() { if (err) { return onAjaxError(err, url, callback, quiet); } - + retry = true; app.template = data.template.name; require(['translator'], function(translator) { @@ -107,20 +108,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; @@ -274,17 +280,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 +311,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..4f7b009a30 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -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); 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/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 21da307713..9046318212 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', $('
').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($('
').html(data.newName).text()); }); }; 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/topic/postTools.js b/public/src/client/topic/postTools.js index 803955c092..28701dd56a 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -28,16 +28,18 @@ 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'); }); }); }); @@ -179,14 +181,16 @@ 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(); + var selectionText = ''; + var selection = window.getSelection ? window.getSelection() : document.selection.createRange(); + var selectionNode = $(selection.baseNode || selection.anchorNode); - if ($(selection.baseNode).parents('[component="post/content"]').length > 0) { + if (selectionNode.parents('[component="post/content"]').length > 0) { selectionText = selection.toString(); } - var username = getUserName(selectionText ? $(selection.baseNode) : button); + button = selectionText ? selectionNode : button; + var username = getUserName(button); if (getData(button, 'data-uid') === '0' || !getData(button, 'data-userslug')) { username = ''; } @@ -306,11 +310,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/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 = $('
').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..ea8fca978a 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; @@ -71,7 +79,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($('
').html(data.newName).text()); }); Chats.onChatEdit(); diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 38737ceaa1..bdc4176d52 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -18,6 +18,7 @@ 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)) { return false; @@ -99,6 +100,23 @@ return style.join('; ') + ';'; }; + helpers.generateChildrenCategories = function(category, relative_path) { + var html = ''; + category.children.forEach(function(child) { + if (!child) { + return; + } + var link = child.link ? child.link : ('/category/' + child.slug); + html += '' + + '' + + '' + + '' + + '' + child.name + ' '; + }); + html = html ? ('
' + html + '') : html; + return html; + }; + helpers.generateTopicClass = function(topic) { var style = []; @@ -243,7 +261,7 @@ } return icons; - } + }; exports.register = function() { var templates; diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index 58c8ef6f26..5892f6dde8 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -215,11 +215,7 @@ 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(/%/g, '%').replace(/,/g, ','); - - variable = S(variables[i]).chompRight(']]').collapseWhitespace().escapeHTML().s; + variable = S(variables[i]).chompRight(']]').collapseWhitespace().decodeHTMLEntities().escapeHTML().s; value = value.replace('%' + i, variable); } 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/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/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index f2c579a05e..0a35315854 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -89,6 +89,8 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) { userData.showHidden = self || 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.sso = results.sso.associations; @@ -100,7 +102,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 +156,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%'; 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'), +var nconf = require('nconf'); +var async = require('async'); +var S = require('string'); - user = require('../../user'), - posts = require('../../posts'), - plugins = require('../../plugins'), - meta = require('../../meta'), - accountHelpers = require('./helpers'), - helpers = require('../helpers'); +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/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 = {}; -infoController.get = function(req, res, next) { +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) { - if (err) { - return next(err); + async.parallel({ + pubsub: function(next) { + pubsub.publish('sync:stats:start'); + next(); + }, + gitInfo: function(next) { + getGitInfo(next); } - data.git = gitInfo; - - res.render('admin/development/info', {info: JSON.stringify(data, null, 4), stats: JSON.stringify(rooms.getStats(), null, 4)}); + }, function(err, results) { + if (err) { + return callback(err); + } + 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/users.js b/src/controllers/admin/users.js index c68fbb8d09..3651767153 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) { 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'), +var async = require('async'); +var validator = require('validator'); +var 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 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); + 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); } - - if (!results.canRead) { - return helpers.notAllowed(req, res); - } - - 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; - async.waterfall([ - function(next) { - user.getUidByUsername(username, next); - }, - function(uid, next) { - getUserByUID(uid, res, next); + apiController.getUserDataByUsername(req.uid, username, function(err, data) { + if (err) { + return next(err); } - ], next); + 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) { + apiController.getUserDataByUID(callerUid, uid, next); + } + ], callback); +}; + +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'), +var async = require('async'); +var winston = require('winston'); +var passport = require('passport'); +var nconf = require('nconf'); +var validator = require('validator'); +var _ = require('underscore'); - db = require('../database'), - meta = require('../meta'), - user = require('../user'), - plugins = require('../plugins'), - utils = require('../../public/src/utils'), - Password = require('../password'), +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'); - authenticationController = {}; +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 = {}; + authenticationController.onSuccessfulLogin(req, uid, callback); + }); +}; - // Associate IP used during login with user account - user.logIP(uid, req.ip); - req.session.meta.ip = req.ip; +authenticationController.onSuccessfulLogin = function(req, uid, callback) { + callback = callback || function() {}; + var uuid = utils.generateUUID(); + req.session.meta = {}; - // 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 - }); + // Associate IP used during login with user account + user.logIP(uid, req.ip); + req.session.meta.ip = req.ip; - // Associate login session with user - user.auth.addSession(uid, req.sessionID); - db.setObjectField('uid:' + uid + 'sessionUUID:sessionId', uuid, req.sessionID); + // 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..f4b939f60a 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -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/groups.js b/src/controllers/groups.js index c77c2c61ee..a67700c0d9 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) { diff --git a/src/controllers/index.js b/src/controllers/index.js index cc83f0727a..51c5c316ba 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -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/topics.js b/src/controllers/topics.js index 5c3728a428..7a222dd09c 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -55,7 +55,11 @@ topicsController.get = function(req, res, callback) { } 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)); + var url = '/topic/' + encodeURI(results.topic.slug); + if (req.params.post_index){ + url += '/'+req.params.post_index; + } + return helpers.redirect(res, url); } var settings = results.settings; @@ -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', 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..8dff27b47c 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -46,15 +46,9 @@ 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); - }); + uploadImage(req.uid, uploadedFile, next); } else { - uploadFile(req.user ? req.user.uid : 0, uploadedFile, next); + uploadFile(req.uid, uploadedFile, next); } }, next); }; @@ -66,7 +60,7 @@ 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); } @@ -82,7 +76,7 @@ uploadsController.uploadThumb = function(req, res, next) { if (err) { return next(err); } - uploadImage(req.user ? req.user.uid : 0, uploadedFile, next); + uploadImage(req.uid, uploadedFile, next); }); } else { next(new Error('[[error:invalid-file]]')); @@ -100,7 +94,12 @@ 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) { @@ -108,11 +107,16 @@ function uploadImage(uid, image, callback) { 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]]')); - } + file.isFileTypeAllowed(image.path, function(err) { + if (err) { + return callback(err); + } + if (parseInt(meta.config.allowFileUploads, 10)) { + uploadFile(uid, image, callback); + } else { + callback(new Error('[[error:uploads-are-disabled]]')); + } + }); } function uploadFile(uid, uploadedFile, callback) { diff --git a/src/controllers/users.js b/src/controllers/users.js index b014ac3452..1eb5989c1c 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); }); @@ -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/sorted.js b/src/database/mongo/sorted.js index ba392fa42a..d209f3f491 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -95,7 +95,16 @@ 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 scoreQuery = {}; + if (min !== '-inf') { + scoreQuery.$gte = min; + } + if (max !== '+inf') { + scoreQuery.$lte = max; + } + + db.collection('objects').remove({_key: {$in: keys}, score: scoreQuery}, function(err) { callback(err); }); }; @@ -125,6 +134,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) @@ -459,6 +473,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(); @@ -533,4 +548,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 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.js b/src/meta/js.js index 3d791d9d3d..d8e4c04143 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'), file = require('../file'), 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/middleware/header.js b/src/middleware/header.js index c2149c16d1..5d981ffeda 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,6 +103,7 @@ 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; @@ -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/notifications.js b/src/notifications.js index 86feff02b4..af88fd7b5b 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,7 +444,7 @@ 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; }); @@ -395,6 +456,10 @@ var async = require('async'), notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers-1) + ', ' + notifications[modifyIndex].topicTitle + ']]'; } break; + + case 'new_register': + notifications[modifyIndex].bodyShort = '[[notifications:' + mergeId + '_multiple, ' + set.length + ']]'; + break; } // Filter out duplicates 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'), +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'); - 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'), +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'); - controllers = require('./controllers'), - app, middleware; +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 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/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,4 +1,40 @@
+
+
+

Info - You are on {host}:{port}

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
hostpidnodejsonlinegitloaduptime
{info.os.hostname}:{info.process.port}{info.process.pid}{info.process.version}{info.stats.onlineRegisteredCount} / {info.stats.onlineGuestCount} / {info.stats.socketCount}{info.git.branch}@{info.git.hash}{info.os.load}{info.process.uptime}
+
+
+
+

Info

@@ -6,19 +42,7 @@
-
{info}
-
-
-
- -
-
-

Stats

-
- -
-
-
{stats}
+
{infoJSON}
diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl index 2fdf04560c..116a0fd0f5 100644 --- a/src/views/admin/general/navigation.tpl +++ b/src/views/admin/general/navigation.tpl @@ -66,6 +66,12 @@ Only display to Admins
+
+ +
- +

@@ -57,7 +57,7 @@
- Posted in {posts.category.name}, • + Posted in {posts.category.name}, Read More diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl index 28cd6c006e..414f51133e 100644 --- a/src/views/admin/manage/group.tpl +++ b/src/views/admin/manage/group.tpl @@ -31,7 +31,7 @@
@@ -41,26 +41,31 @@ -
- - -
-
- -
-
- -
-
- +
+
+ +
+
+ +
+
+ +
+
+
@@ -76,7 +81,7 @@
-

[[groups:details.members]]

+

Member List

diff --git a/src/views/admin/manage/registration.tpl b/src/views/admin/manage/registration.tpl index 278ba279b1..f4dbe697ef 100644 --- a/src/views/admin/manage/registration.tpl +++ b/src/views/admin/manage/registration.tpl @@ -44,7 +44,7 @@ {users.ip} - +
@@ -66,18 +66,23 @@

The username will be displayed to the right of the emails for users who have redeemed their invitations.

- +
- - - + + + - + diff --git a/src/views/admin/settings/user.tpl b/src/views/admin/settings/user.tpl index cf5360d5fb..08ae01fe68 100644 --- a/src/views/admin/settings/user.tpl +++ b/src/views/admin/settings/user.tpl @@ -178,40 +178,40 @@
- + - [[user:send_chat_notifications]] + Send an email if a new chat message arrives and I am not online
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);
Inviter Username Invitee Email Invitee Username (if registered)
{invites.username}
{invites.username} {invites.invitations.email}{invites.invitations.username}{invites.invitations.username} +
+ +
+