diff --git a/.eslintrc b/.eslintrc index bbba3aa94e..4f7d4e2413 100644 --- a/.eslintrc +++ b/.eslintrc @@ -54,9 +54,9 @@ "no-restricted-syntax": "off", "no-script-url": "off", "default-case": "off", + "linebreak-style": "off", // "no-multi-assign": "off", - // "linebreak-style": "off", // "one-var": "off", // "no-undef": "off", // "max-nested-callbacks": "off", diff --git a/.gitignore b/.gitignore index dcda0224fc..469b990863 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ pidfile /public/templates /public/sounds +/public/uploads # compiled files /public/stylesheet.css diff --git a/.travis.yml b/.travis.yml index 69688f4bde..e9db303b27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ addons: packages: - g++-4.8 node_js: + - "7" - "6" - "4" branches: diff --git a/.tx/config b/.tx/config index e85414a079..d88ed808e1 100644 --- a/.tx/config +++ b/.tx/config @@ -769,6 +769,53 @@ trans.zh_CN = public/language/zh-CN/error.json trans.zh_TW = public/language/zh-TW/error.json type = KEYVALUEJSON +[nodebb.flags] +file_filter = public/language//flags.json +source_file = public/language/en-GB/flags.json +source_lang = en_GB +trans.ar = public/language/ar/flags.json +trans.bn = public/language/bn/flags.json +trans.bg = public/language/bg/flags.json +trans.cs = public/language/cs/flags.json +trans.da = public/language/da/flags.json +trans.de = public/language/de/flags.json +trans.el = public/language/el/flags.json +trans.en_US = public/language/en-US/flags.json +trans.en@pirate = public/language/en-x-pirate/flags.json +trans.es = public/language/es/flags.json +trans.et = public/language/et/flags.json +trans.fa_IR = public/language/fa-IR/flags.json +trans.fi = public/language/fi/flags.json +trans.fr = public/language/fr/flags.json +trans.gl = public/language/gl/flags.json +trans.he = public/language/he/flags.json +trans.hu = public/language/hu/flags.json +trans.id = public/language/id/flags.json +trans.it = public/language/it/flags.json +trans.ja = public/language/ja/flags.json +trans.ko = public/language/ko/flags.json +trans.lt = public/language/lt/flags.json +trans.ms = public/language/ms/flags.json +trans.nb = public/language/nb/flags.json +trans.nl = public/language/nl/flags.json +trans.pl = public/language/pl/flags.json +trans.pt_BR = public/language/pt-BR/flags.json +trans.pt_PT = public/language/pt-PT/flags.json +trans.ru = public/language/ru/flags.json +trans.ro = public/language/ro/flags.json +trans.rw = public/language/rw/flags.json +trans.sc = public/language/sc/flags.json +trans.sk = public/language/sk/flags.json +trans.sl = public/language/sl/flags.json +trans.sr = public/language/sr/flags.json +trans.sv = public/language/sv/flags.json +trans.th = public/language/th/flags.json +trans.tr = public/language/tr/flags.json +trans.vi = public/language/vi/flags.json +trans.zh_CN = public/language/zh-CN/flags.json +trans.zh_TW = public/language/zh-TW/flags.json +type = KEYVALUEJSON + [nodebb.tags] file_filter = public/language//tags.json source_file = public/language/en-GB/tags.json @@ -2065,54 +2112,6 @@ trans.zh_CN = public/language/zh-CN/admin/manage/categories.json trans.zh_TW = public/language/zh-TW/admin/manage/categories.json type = KEYVALUEJSON -[nodebb.admin-manage-flags] -file_filter = public/language//admin/manage/flags.json -source_file = public/language/en-GB/admin/manage/flags.json -source_lang = en_GB -trans.ar = public/language/ar/admin/manage/flags.json -trans.bn = public/language/bn/admin/manage/flags.json -trans.bg = public/language/bg/admin/manage/flags.json -trans.cs = public/language/cs/admin/manage/flags.json -trans.da = public/language/da/admin/manage/flags.json -trans.de = public/language/de/admin/manage/flags.json -trans.el = public/language/el/admin/manage/flags.json -trans.en_US = public/language/en-US/admin/manage/flags.json -trans.en@pirate = public/language/en-x-pirate/admin/manage/flags.json -trans.es = public/language/es/admin/manage/flags.json -trans.et = public/language/et/admin/manage/flags.json -trans.fa_IR = public/language/fa-IR/admin/manage/flags.json -trans.fi = public/language/fi/admin/manage/flags.json -trans.fr = public/language/fr/admin/manage/flags.json -trans.gl = public/language/gl/admin/manage/flags.json -trans.he = public/language/he/admin/manage/flags.json -trans.hu = public/language/hu/admin/manage/flags.json -trans.id = public/language/id/admin/manage/flags.json -trans.it = public/language/it/admin/manage/flags.json -trans.ja = public/language/ja/admin/manage/flags.json -trans.ko = public/language/ko/admin/manage/flags.json -trans.lt = public/language/lt/admin/manage/flags.json -trans.ms = public/language/ms/admin/manage/flags.json -trans.nb = public/language/nb/admin/manage/flags.json -trans.nl = public/language/nl/admin/manage/flags.json -trans.pl = public/language/pl/admin/manage/flags.json -trans.pt_BR = public/language/pt-BR/admin/manage/flags.json -trans.pt_PT = public/language/pt-PT/admin/manage/flags.json -trans.ru = public/language/ru/admin/manage/flags.json -trans.ro = public/language/ro/admin/manage/flags.json -trans.rw = public/language/rw/admin/manage/flags.json -trans.sc = public/language/sc/admin/manage/flags.json -trans.sk = public/language/sk/admin/manage/flags.json -trans.sl = public/language/sl/admin/manage/flags.json -trans.sr = public/language/sr/admin/manage/flags.json -trans.sv = public/language/sv/admin/manage/flags.json -trans.th = public/language/th/admin/manage/flags.json -trans.tr = public/language/tr/admin/manage/flags.json -trans.uk = public/language/uk/admin/manage/flags.json -trans.vi = public/language/vi/admin/manage/flags.json -trans.zh_CN = public/language/zh-CN/admin/manage/flags.json -trans.zh_TW = public/language/zh-TW/admin/manage/flags.json -type = KEYVALUEJSON - [nodebb.admin-manage-groups] file_filter = public/language//admin/manage/groups.json source_file = public/language/en-GB/admin/manage/groups.json diff --git a/app.js b/app.js index f1412532a5..26336a74c8 100644 --- a/app.js +++ b/app.js @@ -194,13 +194,17 @@ function upgrade() { var meta = require('./src/meta'); var upgrade = require('./src/upgrade'); var build = require('./src/meta/build'); + var tasks = [db.init, meta.configs.init, upgrade.run, build.buildAll]; - async.series([ - async.apply(db.init), - async.apply(meta.configs.init), - async.apply(upgrade.upgrade), - async.apply(build.buildAll), - ], function (err) { + if (nconf.get('upgrade') !== true) { + // Likely an upgrade script name passed in + tasks[2] = async.apply(upgrade.runSingle, nconf.get('upgrade')); + + // Skip build + tasks.pop(); + } + + async.series(tasks, function (err) { if (err) { winston.error(err.stack); process.exit(1); diff --git a/install/data/defaults.json b/install/data/defaults.json index c471db6b89..79fbcf07f4 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -17,6 +17,7 @@ "allowLocalLogin": 1, "allowAccountDelete": 1, "allowFileUploads": 0, + "allowedFileExtensions": "png,jpg,bmp", "allowUserHomePage": 1, "maximumFileSize": 2048, "minimumTitleLength": 3, @@ -28,12 +29,13 @@ "maximumAboutMeLength": 1000, "maximumProfileImageSize": 256, "maximumCoverImageSize": 2048, - "profileImageDimension": 128, + "profileImageDimension": 200, "requireEmailConfirmation": 0, "allowProfileImageUploads": 1, "teaserPost": "last-reply", "allowPrivateGroups": 1, "unreadCutoff": 2, "bookmarkThreshold": 5, - "topicsPerList": 20 + "topicsPerList": 20, + "autoDetectLang": 1 } diff --git a/nodebb b/nodebb index 447f17c9a6..22051bb810 100755 --- a/nodebb +++ b/nodebb @@ -428,6 +428,18 @@ var commands = { description: 'Run NodeBB upgrade scripts, ensure packages are up-to-date', usage: 'Usage: ' + './nodebb upgrade'.yellow, handler: function () { + if (process.argv[3]) { + process.stdout.write('\nUpdating NodeBB data store schema...\n'.yellow); + var arr = ['--upgrade'].concat(process.argv.slice(3)); + var upgradeProc = fork(arr); + + return upgradeProc.on('close', function (err) { + if (err) { + process.stdout.write('\nError'.red + ': ' + err.message + '\n'); + } + }); + } + async.series([ function (next) { process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... '.yellow); diff --git a/package.json b/package.json index 6f8ae9706e..b4a6a96933 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.4.6", + "version": "1.5.0", "homepage": "http://www.nodebb.org", "repository": { "type": "git", @@ -17,10 +17,10 @@ "coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" }, "dependencies": { + "async": "2.4.0", "ace-builds": "^1.2.6", - "async": "~1.5.0", - "autoprefixer": "^6.2.3", - "bcryptjs": "~2.3.0", + "autoprefixer": "7.0.1", + "bcryptjs": "2.4.3", "body-parser": "^1.9.0", "bootstrap": "^3.3.7", "chart.js": "^2.4.0", @@ -30,7 +30,7 @@ "connect-flash": "^0.1.1", "connect-mongo": "1.3.2", "connect-multiparty": "^2.0.0", - "connect-redis": "~3.1.0", + "connect-redis": "3.3.0", "cookie-parser": "^1.3.3", "cron": "^1.0.5", "cropperjs": "^0.8.1", @@ -39,65 +39,65 @@ "express": "^4.14.0", "express-session": "^1.8.2", "express-useragent": "1.0.7", - "html-to-text": "2.1.3", - "ip": "1.1.3", + "html-to-text": "3.2.0", + "ip": "1.1.5", "jimp": "0.2.27", "jquery": "^3.1.0", "json-2-csv": "^2.0.22", "less": "^2.0.0", "logrotate-stream": "^0.2.3", - "lru-cache": "4.0.1", + "lru-cache": "4.0.2", "mime": "^1.3.4", "minimist": "^1.1.1", "mkdirp": "~0.5.0", - "mongodb": "2.2.25", + "mongodb": "2.2.26", "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "4.4.6", - "nodebb-plugin-dbsearch": "1.0.5", + "nodebb-plugin-composer-default": "4.4.7", + "nodebb-plugin-dbsearch": "2.0.2", "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.1.5", "nodebb-plugin-markdown": "7.1.1", - "nodebb-plugin-mentions": "1.1.3", + "nodebb-plugin-mentions": "2.0.3", "nodebb-plugin-soundpack-default": "1.0.0", "nodebb-plugin-spam-be-gone": "0.4.13", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "4.0.0", - "nodebb-theme-persona": "4.2.10", - "nodebb-theme-vanilla": "5.2.1", - "nodebb-widget-essentials": "2.0.13", + "nodebb-theme-persona": "5.0.0", + "nodebb-theme-vanilla": "6.0.0", + "nodebb-widget-essentials": "3.0.0", "nodemailer": "2.6.4", "nodemailer-sendmail-transport": "1.0.0", "nodemailer-smtp-transport": "^2.4.1", "passport": "^0.3.0", "passport-local": "1.0.0", - "postcss": "^5.0.13", - "postcss-clean": "^1.0.0", + "postcss": "6.0.1", + "postcss-clean": "1.0.2", "promise-polyfill": "^6.0.2", "prompt": "^1.0.0", - "redis": "~2.6.2", - "request": "^2.44.0", - "rimraf": "~2.5.0", + "redis": "2.7.1", + "request": "2.81.0", + "rimraf": "2.6.1", "rss": "^1.0.0", "sanitize-html": "^1.13.0", "semver": "^5.1.0", "serve-favicon": "^2.1.5", "sitemap": "^1.4.0", - "socket.io": "1.7.2", - "socket.io-client": "1.7.2", - "socket.io-redis": "3.1.0", - "socketio-wildcard": "~0.3.0", + "socket.io": "1.7.4", + "socket.io-client": "1.7.4", + "socket.io-redis": "4.0.0", + "socketio-wildcard": "0.4.0", "string": "^3.0.0", "templates.js": "0.3.10", "toobusy-js": "^0.5.1", "uglify-js": "^2.6.0", "underscore": "^1.8.3", "underscore.deep": "^0.5.1", - "validator": "^6.1.0", + "validator": "7.0.0", "winston": "^2.1.0", "xml": "^1.0.1", - "xregexp": "~3.1.0", + "xregexp": "3.2.0", "zxcvbn": "^4.4.2" }, "devDependencies": { diff --git a/public/language/ar/admin/advanced/database.json b/public/language/ar/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/ar/admin/advanced/database.json +++ b/public/language/ar/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/ar/admin/manage/flags.json b/public/language/ar/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/ar/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/ar/admin/manage/groups.json b/public/language/ar/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/ar/admin/manage/groups.json +++ b/public/language/ar/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/ar/admin/settings/advanced.json b/public/language/ar/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/ar/admin/settings/advanced.json +++ b/public/language/ar/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/ar/admin/settings/post.json b/public/language/ar/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/ar/admin/settings/post.json +++ b/public/language/ar/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/ar/admin/settings/user.json b/public/language/ar/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/ar/admin/settings/user.json +++ b/public/language/ar/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/ar/email.json b/public/language/ar/email.json index 67171b01a6..8e0fba7424 100644 --- a/public/language/ar/email.json +++ b/public/language/ar/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "تم إشعارك بهذه المشاركة بناءً على الخيارات التي سبق وأن حددتها.", "test.text1": "هذه رسالة تجريبية للتأكد من صحة إعدادت الرسائل الإلكترونية في منتدى NodeBB خاصتك.", "unsub.cta": "انقر هنا لتغيير تلك الإعدادات", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "شكرًا لك!" } \ No newline at end of file diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 61d2a6e3a6..0a05be7777 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -30,6 +30,7 @@ "password-too-long": "كلمة السر طويلة ", "user-banned": "المستخدم محظور", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "عذرا, يجب أن تنتظر 1% ثواني قبل قيامك بأول مشاركة", "blacklisted-ip": "نأسف، لقد تم حظرك من استخدام وتصفح المنتدى. إذا كنت تعتقد أن هذا خطأ رجاءًا اتصل بالإدارة. ", "ban-expiry-missing": "رجاءًا ضع تاريخ نهاية الحظر. ", @@ -104,7 +105,7 @@ "chat-disabled": "نظام المحادثة معطل.", "too-many-messages": "لقد أرسلت الكثير من الرسائل، الرجاء اﻹنتظار قليلاً", "invalid-chat-message": "الرسالة غير صالحة.", - "chat-message-too-long": "الرسالة طويلة.", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "غير مصرح لك بتعديل الرسالة.", "cant-remove-last-user": "لأيمكنك إزالت اخر مستخدم.", "cant-delete-chat-message": "غير مصرح لك بحذف الرسالة.", diff --git a/public/language/ar/flags.json b/public/language/ar/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/ar/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json index 1e844226a2..c975207938 100644 --- a/public/language/ar/modules.json +++ b/public/language/ar/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 أشهر", "chat.delete_message_confirm": "هل أنت متأكد من أنك تريد حذف هذه الرسالة؟", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "اكتب", "composer.show_preview": "عرض المعاينة", "composer.hide_preview": "إخفاء المعاينة", diff --git a/public/language/ar/user.json b/public/language/ar/user.json index fc26490197..92841dfd93 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "اسم المستخدم الذي اخترته سبق أخذه، لذا تم تغييره قليلا. أن الآن مسجل تحت الاسم %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "ارفع الصورة", "upload_a_picture": "رفع صورة", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/bg/admin/advanced/database.json b/public/language/bg/admin/advanced/database.json index 051b6ad293..acc6fde389 100644 --- a/public/language/bg/admin/advanced/database.json +++ b/public/language/bg/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 Б", "x-mb": "%1 МБ", + "x-gb": "%1 ГБ", "uptime-seconds": "Активно време в секунди", "uptime-days": "Активно време в дни", diff --git a/public/language/bg/admin/manage/flags.json b/public/language/bg/admin/manage/flags.json deleted file mode 100644 index cf5e368b75..0000000000 --- a/public/language/bg/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Дневни доклади", - "by-user": "Доклади по потребител", - "by-user-search": "Търсене на докладвани публикации по потребителско име", - "category": "Категория", - "sort-by": "Подреждане по", - "sort-by.most-flags": "Най-много доклади", - "sort-by.most-recent": "Най-скорошни", - "search": "Търсене", - "dismiss-all": "Премахване на всички", - "none-flagged": "Няма докладвани публикации!", - "posted-in": "Публикувано в %1", - "read-more": "Прочетете повече", - "flagged-x-times": "Тази публикация е докладвана %1 път(и):", - "dismiss": "Премахване на този доклад", - "delete-post": "Изтриване на публикацията", - - "alerts.confirm-delete-post": "Наистина ли искате да изтриете тази публикация?" -} \ No newline at end of file diff --git a/public/language/bg/admin/manage/groups.json b/public/language/bg/admin/manage/groups.json index 0eb53e4d0c..d7e9a46ca2 100644 --- a/public/language/bg/admin/manage/groups.json +++ b/public/language/bg/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Име на групата", "description": "Описание на групата", + "member-count": "Брой на членовете", "system": "Системна група", "edit": "Редактиране", "search-placeholder": "Търсене", diff --git a/public/language/bg/admin/settings/advanced.json b/public/language/bg/admin/settings/advanced.json index 44998c70b0..d99bdfd5b5 100644 --- a/public/language/bg/admin/settings/advanced.json +++ b/public/language/bg/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Задайте „ALLOW-FROM“, за да поставите NodeBB в „iFrame“", "headers.powered-by": "Персонализиране на заглавната част „Захранван от“, която се изпраща от NodeBB", "headers.acao": "Произход за разрешаване на управлението на достъпа", - "headers.acao-help": "За да забраните достъпа до всички уеб сайтове, оставете празно или задайте null", + "headers.acao-help": "За да забраните достъпа до всички уеб сайтове, оставете празно", "headers.acam": "Методи за разрешаване на управлението на достъпа", "headers.acah": "Заглавки за разрешаване на управлението на достъпа", "traffic-management": "Управление на трафика", diff --git a/public/language/bg/admin/settings/post.json b/public/language/bg/admin/settings/post.json index 7a867fd34a..37b535660f 100644 --- a/public/language/bg/admin/settings/post.json +++ b/public/language/bg/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Настройки за непрочетените", "unread.cutoff": "Възраст на публикациите, след която те не се показват в непрочетените (в брой дни)", "unread.min-track-last": "Минимален брой публикации в темата, след което да започва следене на последно прочетената", + "recent": "Настройки за скорошните", + "recent.categoryFilter.disable": "Изключване на филтрирането на темите в пренебрегваните категории на страницата /recent", "signature": "Настройки за подписите", "signature.disable": "Забраняване на подписите", "signature.no-links": "Забраняване на поставянето на връзки в подписите", diff --git a/public/language/bg/admin/settings/user.json b/public/language/bg/admin/settings/user.json index 33efa26d71..a470c905d0 100644 --- a/public/language/bg/admin/settings/user.json +++ b/public/language/bg/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Минимална дължина на потребителското име", "max-username-length": "Максимална дължина на потребителското име", "min-password-length": "Минимална дължина на паролата", + "min-password-strength": "Минимална сложност на паролата", "max-about-me-length": "Максимална дължина на информацията на потребителите за себе си", "terms-of-use": "Условия за ползване на форума (Оставете празно и няма да има такива)", "user-search": "Търсене на потребители", diff --git a/public/language/bg/email.json b/public/language/bg/email.json index 9ee562dd11..aaf94585fd 100644 --- a/public/language/bg/email.json +++ b/public/language/bg/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Това известие за публикация беше изпратено до Вас поради настройките Ви за абонаментите.", "test.text1": "Това е пробно е-писмо, за да потвърдим, че изпращачът на е-поща е правилно настроен за Вашия NodeBB.", "unsub.cta": "Натиснете тук, за да промените тези настройки", + "banned.subject": "Вие бяхте блокиран(а) от %1", + "banned.text1": "Потребителят %1 беше блокиран от %2.", + "banned.text2": "Това блокиране ще е в сила до %1.", + "banned.text3": "Това е причината, поради която бяхте блокиран(а):", "closing": "Благодарим Ви!" } \ No newline at end of file diff --git a/public/language/bg/error.json b/public/language/bg/error.json index fa06dea5a9..6733bdcca5 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -30,6 +30,7 @@ "password-too-long": "Паролата е твърде дълга", "user-banned": "Потребителят е блокиран", "user-banned-reason": "За съжаление, този акаунт е блокиран (Причина: %1)", + "user-banned-reason-until": "За съжаление, този акаунт е блокиран до %1 (Причина: %2)", "user-too-new": "Съжаляваме, но трябва да изчакате поне %1 секунда/и, преди да направите първата си публикация", "blacklisted-ip": "Съжаляваме, но Вашият IP адрес е забранен за ползване в тази общност. Ако смятате, че това е грешка, моля, свържете се с администратор.", "ban-expiry-missing": "Моля, задайте крайна дата за това блокиране", @@ -104,7 +105,7 @@ "chat-disabled": "Системата за разговори е изключена", "too-many-messages": "Изпратили сте твърде много съобщения. Моля, изчакайте малко.", "invalid-chat-message": "Неправилно съобщение", - "chat-message-too-long": "Съобщението е твърде дълго", + "chat-message-too-long": "Съобщенията в разговор не може да бъдат по-дълги от %1 знака.", "cant-edit-chat-message": "Нямате право да редактирате това съобщение", "cant-remove-last-user": "Не можете да премахнете последния потребител", "cant-delete-chat-message": "Нямате право да изтриете това съобщение", diff --git a/public/language/bg/flags.json b/public/language/bg/flags.json new file mode 100644 index 0000000000..138feac5c5 --- /dev/null +++ b/public/language/bg/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Състояние", + "reporter": "Докладвал", + "reported-at": "Докладвано на", + "description": "Описание", + "no-flags": "Ура! Няма намерени доклади.", + "assignee": "Назначен", + "update": "Обновяване", + "updated": "Обновено", + "target-purged": "Съдържанието, за което се отнася този доклад, е било изтрито и вече не е налично.", + + "quick-filters": "Бързи филтри", + "filter-active": "В този списък с доклади има един или повече филтри", + "filter-reset": "Премахване на филтрите", + "filters": "Настройки на филтрите", + "filter-reporterId": "Потр. ид. на докладвалия", + "filter-targetUid": "Потр. ид. на докладвания", + "filter-type": "Вид на доклада", + "filter-type-all": "Всичко", + "filter-type-post": "Публикация", + "filter-state": "Състояние", + "filter-assignee": "Потр. ид. на назначения", + "filter-cid": "Категория", + "filter-quick-mine": "Назначени на мен", + "filter-cid-all": "Всички категории", + "apply-filters": "Прилагане на филтрите", + + "quick-links": "Бързи връзки", + "flagged-user": "Докладван потребител", + "view-profile": "Преглед на профила", + "start-new-chat": "Започване на нов разговор", + "go-to-target": "Преглед на целта на доклада", + + "user-view": "Преглед на профила", + "user-edit": "Редактиране на профила", + + "notes": "Бележки към доклада", + "add-note": "Добавяне на бележка", + "no-notes": "Няма споделени бележки.", + + "history": "История на доклада", + "back": "Обратно към списъка с доклади", + "no-history": "Няма история на доклада.", + + "state-all": "Всички състояния", + "state-open": "Нов/отворен", + "state-wip": "В процес на работа", + "state-resolved": "Разрешен", + "state-rejected": "Отхвърлен", + "no-assignee": "Без назначение", + "note-added": "Бележката е добавена", + + "modal-title": "Докладване на неуместно съдържание", + "modal-body": "Моля, посочете причината за докладването на %1 %2 за преглед. Или използвайте някой от бутоните за бързо докладване, ако са приложими.", + "modal-reason-spam": "Спам", + "modal-reason-offensive": "Обидно", + "modal-reason-custom": "Причина за докладването на това съдържание…", + "modal-submit": "Изпращане на доклада", + "modal-submit-success": "Съдържанието беше докладвано на модераторите." +} \ No newline at end of file diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index b459ebaa2a..fac82100cb 100644 --- a/public/language/bg/modules.json +++ b/public/language/bg/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 месеца", "chat.delete_message_confirm": "Наистина ли искате да изтриете това съобщение?", "chat.add-users-to-room": "Добавяне на потребители към стаята", + "chat.confirm-chat-with-dnd-user": "Този потребител е в състояние „не ме безпокойте“. Наистина ли искате да разговаряте с него?", "composer.compose": "Писане", "composer.show_preview": "Показване на прегледа", "composer.hide_preview": "Скриване на прегледа", diff --git a/public/language/bg/user.json b/public/language/bg/user.json index 3f47b9db1b..ac0249f530 100644 --- a/public/language/bg/user.json +++ b/public/language/bg/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Потребителското име, което искате, е заето и затова ние го променихме малко. Вие ще се наричате %1", "password_same_as_username": "Паролата е същата като потребителското Ви име. Моля, изберете друга парола.", "password_same_as_email": "Паролата е същата като е-пощата Ви. Моля, изберете друга парола.", + "weak_password": "Проста парола.", "upload_picture": "Качване на снимка", "upload_a_picture": "Качване на снимка", "remove_uploaded_picture": "Премахване на качената снимка", diff --git a/public/language/bn/admin/advanced/database.json b/public/language/bn/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/bn/admin/advanced/database.json +++ b/public/language/bn/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/bn/admin/manage/flags.json b/public/language/bn/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/bn/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/bn/admin/manage/groups.json b/public/language/bn/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/bn/admin/manage/groups.json +++ b/public/language/bn/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/bn/admin/settings/advanced.json b/public/language/bn/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/bn/admin/settings/advanced.json +++ b/public/language/bn/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/bn/admin/settings/post.json b/public/language/bn/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/bn/admin/settings/post.json +++ b/public/language/bn/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/bn/admin/settings/user.json b/public/language/bn/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/bn/admin/settings/user.json +++ b/public/language/bn/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/bn/email.json b/public/language/bn/email.json index 525460a206..c4b24d267c 100644 --- a/public/language/bn/email.json +++ b/public/language/bn/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "আপনার সাবস্ক্রিপশন সেটিংসের কারনে আপনার এই বার্তাটি পাঠানো হয়েছে", "test.text1": "আপনি সঠিকভাবে নোডবিবির জন্য মেইলার সেটাপ করেছেন কিনা নিশ্চিত করার জন্য এই টেষ্ট ইমেইল পাঠানো হয়েছে", "unsub.cta": "সেটিংসগুলো পরিবর্তন করতে এখানে ক্লিক করুন", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "ধন্যবাদ!" } \ No newline at end of file diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 1141892e4a..5c97eecb55 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "ব্যবহারকারী নিষিদ্ধ", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/bn/flags.json b/public/language/bn/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/bn/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/bn/modules.json b/public/language/bn/modules.json index 29b4a81e69..c512813734 100644 --- a/public/language/bn/modules.json +++ b/public/language/bn/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "৩ মাস", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/bn/user.json b/public/language/bn/user.json index 8e85a9dcca..765d2d9b60 100644 --- a/public/language/bn/user.json +++ b/public/language/bn/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "আপনি যে ইউজারনেম চাচ্ছিলেন সেটি ইতিমধ্যে নেয়া হয়ে গেছে, কাজেই আমরা এটি কিঞ্চিং পরিবর্তন করেছি। আপনি এখন %1 হিসেবে পরিচিত", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "ছবি আপলোড করুন", "upload_a_picture": "ছবি (একটি) আপলোড করুন", "remove_uploaded_picture": "আপলোড করা ছবিটি সরিয়ে নাও", diff --git a/public/language/cs/admin/advanced/database.json b/public/language/cs/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/cs/admin/advanced/database.json +++ b/public/language/cs/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/cs/admin/manage/flags.json b/public/language/cs/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/cs/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/cs/admin/manage/groups.json b/public/language/cs/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/cs/admin/manage/groups.json +++ b/public/language/cs/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/cs/admin/settings/advanced.json b/public/language/cs/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/cs/admin/settings/advanced.json +++ b/public/language/cs/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/cs/admin/settings/post.json b/public/language/cs/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/cs/admin/settings/post.json +++ b/public/language/cs/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/cs/admin/settings/user.json b/public/language/cs/admin/settings/user.json index 607a2fbd92..c1684da402 100644 --- a/public/language/cs/admin/settings/user.json +++ b/public/language/cs/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimální délka uživatelského jména", "max-username-length": "Maximální délka uživatelského jména", "min-password-length": "Minimální délka hesla", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximální délka hesla", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/cs/email.json b/public/language/cs/email.json index 1070b21754..544e2ab30f 100644 --- a/public/language/cs/email.json +++ b/public/language/cs/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Toto upozornění na příspěvek vám bylo odesláno na základě vašeho nastavení odběru.", "test.text1": "Tento testovací e-mail slouží k ověření, že je e-mailer správně nastaven pro práci s NodeBB.", "unsub.cta": "Chcete-li změnit tyto nastavení, klikněte zde.", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Díky!" } \ No newline at end of file diff --git a/public/language/cs/error.json b/public/language/cs/error.json index d0959ae770..41ca155c7a 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -30,6 +30,7 @@ "password-too-long": "Heslo je příliš dlouhé", "user-banned": "Uživatel byl zakázán", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/cs/flags.json b/public/language/cs/flags.json new file mode 100644 index 0000000000..aea0e868e4 --- /dev/null +++ b/public/language/cs/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Důvod nahlášení tohoto obsahu…", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index 84afa3b052..3bc708e825 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 měsíce", "chat.delete_message_confirm": "Jste si jisti že chcete odstranit tuto zprávu?", "chat.add-users-to-room": "Přidat uživatele do místnosti", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Napsat", "composer.show_preview": "Ukázat náhled", "composer.hide_preview": "Skrýt náhled", diff --git a/public/language/cs/pages.json b/public/language/cs/pages.json index 0619e12dcc..99962b5908 100644 --- a/public/language/cs/pages.json +++ b/public/language/cs/pages.json @@ -5,21 +5,21 @@ "popular-week": "Oblíbená témata pro tento týden", "popular-month": "Oblíbená témata pro tento měsíc", "popular-alltime": "Oblíbená témata za celou dobu", - "recent": "Aktuální témata", - "flagged-content": "Flagged Content", - "ip-blacklist": "IP Blacklist", + "recent": "Současná témata", + "flagged-content": "Nahlášený obsah", + "ip-blacklist": "Černá listina IP adres", "users/online": "Uživatelé online", "users/latest": "Nejnovější uživatelé", "users/sort-posts": "Uživatelé s nejvíce příspěvky", "users/sort-reputation": "Uživatelé s nejlepší reputací", - "users/banned": "Zabanovaní uživatelé", + "users/banned": "Zablokovaní uživatelé", "users/most-flags": "Most flagged users", "users/search": "Hledání uživatele", "notifications": "Upozornění", "tags": "Tagy", "tag": "Téma označeno pod \"%1\"", "register": "Zaregistrovat účet", - "registration-complete": "Registration complete", + "registration-complete": "Registrace dokončena", "login": "Přihlásit se ke svému účtu", "reset": "Obnovit heslo k účtu", "categories": "Kategorie", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index 1e31313032..63560d8b42 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Zvolené uživatelské jméno je již zabrané, takže jsme ho trochu upravili. Nyní jste znám jako %1", "password_same_as_username": "Vaše heslo je stejné jako vaše přihlašovací jméno. Zvolte si prosím jiné heslo.", "password_same_as_email": "Vaše heslo je stejné jako váš e-mail. Zvolte si prosím jiné heslo.", + "weak_password": "Weak password.", "upload_picture": "Nahrát obrázek", "upload_a_picture": "Nahrát obrázek", "remove_uploaded_picture": "Odstranit nahraný obrázek", diff --git a/public/language/da/admin/advanced/database.json b/public/language/da/admin/advanced/database.json index 59742a0158..42ae5af00c 100644 --- a/public/language/da/admin/advanced/database.json +++ b/public/language/da/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Oppetid i Sekunder", "uptime-days": "Oppetid i Dage", diff --git a/public/language/da/admin/manage/flags.json b/public/language/da/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/da/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/da/admin/manage/groups.json b/public/language/da/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/da/admin/manage/groups.json +++ b/public/language/da/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/da/admin/settings/advanced.json b/public/language/da/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/da/admin/settings/advanced.json +++ b/public/language/da/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/da/admin/settings/post.json b/public/language/da/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/da/admin/settings/post.json +++ b/public/language/da/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/da/admin/settings/user.json b/public/language/da/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/da/admin/settings/user.json +++ b/public/language/da/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/da/email.json b/public/language/da/email.json index d5591d698d..afd7577d19 100644 --- a/public/language/da/email.json +++ b/public/language/da/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Denne indlægs notifikation var sendt pga. dine abonnering indstillinger.", "test.text1": "Dette er en test email for at kontrollere, at den udgående email server er opsat korrekt i forhold til din NodeBB installation.", "unsub.cta": "Klik her for at ændre disse indstillinger", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Tak!" } \ No newline at end of file diff --git a/public/language/da/error.json b/public/language/da/error.json index 331a56891e..271adb8fd3 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -30,6 +30,7 @@ "password-too-long": "Kodeord er for langt", "user-banned": "Bruger er bortvist", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Beklager, du er nødt til at vente %1 sekund(er) før du opretter dit indlæg", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "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", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/da/flags.json b/public/language/da/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/da/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/da/modules.json b/public/language/da/modules.json index 819631f988..7a96180041 100644 --- a/public/language/da/modules.json +++ b/public/language/da/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 måneder", "chat.delete_message_confirm": "Er du sikker på at du vil slette denne besked?", "chat.add-users-to-room": "Tilføj brugere til chatrum", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Skriv", "composer.show_preview": "Vis forhåndsvisning", "composer.hide_preview": "Fjern forhåndsvisning", diff --git a/public/language/da/user.json b/public/language/da/user.json index 36f08d5a53..efe3d846f8 100644 --- a/public/language/da/user.json +++ b/public/language/da/user.json @@ -60,6 +60,7 @@ "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": "Dit kodeord er det samme som din email, venligst vælg et andet kodeord", + "weak_password": "Weak password.", "upload_picture": "Upload billede", "upload_a_picture": "Upload et billede", "remove_uploaded_picture": "Fjern uploaded billede", diff --git a/public/language/de/admin/advanced/cache.json b/public/language/de/admin/advanced/cache.json index 75a8f8a48c..ce20ed57ee 100644 --- a/public/language/de/admin/advanced/cache.json +++ b/public/language/de/admin/advanced/cache.json @@ -1,11 +1,11 @@ { - "post-cache": "Eintrag Zwischenspeicher", - "posts-in-cache": "Einträge im Zwischenspeicher", - "average-post-size": "Durchschnittliche Forum Eintrags Größe", + "post-cache": "Beitrags Cache", + "posts-in-cache": "Beiträge im Cache", + "average-post-size": "Durchschnittliche Beitragsgröße", "length-to-max": "Länge / Maximum", "percent-full": "%1% Voll", - "post-cache-size": "Eintrags Zwischenspeicher Größe", - "items-in-cache": "Objekte im Zwischenspeicher", + "post-cache-size": "Beitrags Cache Größe", + "items-in-cache": "Objekte im Cache", "control-panel": "Systemsteuerung", "update-settings": "Aktualisiere Zwischenspeicher Einstellungen" } \ No newline at end of file diff --git a/public/language/de/admin/advanced/database.json b/public/language/de/admin/advanced/database.json index eebefbb703..a2b3963632 100644 --- a/public/language/de/admin/advanced/database.json +++ b/public/language/de/admin/advanced/database.json @@ -1,12 +1,13 @@ { "x-b": "%1 B", "x-mb": "%1 MB", - "uptime-seconds": "Laufzeit in Sekunden", - "uptime-days": "Laufzeit in Tagen", + "x-gb": "%1 gb", + "uptime-seconds": "Uptime in Sekunden", + "uptime-days": "Uptime in Tagen", "mongo": "Mongo", "mongo.version": "MongoDB Version", - "mongo.storage-engine": "Speicherengine", + "mongo.storage-engine": "Storage Engine", "mongo.collections": "Collections", "mongo.objects": "Objekte", "mongo.avg-object-size": "Durchschnittliche Objektgröße", @@ -26,7 +27,7 @@ "redis.blocked-clients": "Blockierte Clients", "redis.used-memory": "Speicherverbrauch", "redis.memory-frag-ratio": "Speicherfragmentierungsgrad", - "redis.total-connections-recieved": "Insgesamt Verbindungen empfangen", + "redis.total-connections-recieved": "Gesamte empfangen Verbindungen", "redis.total-commands-processed": "Insgesamt Kommandos ausgeführt", "redis.iops": "Durchschnittliche Anzahl von Ein-/Ausgaben pro Sekunde", "redis.keyspace-hits": "Schlüsselraum Treffer", diff --git a/public/language/de/admin/advanced/errors.json b/public/language/de/admin/advanced/errors.json index 42c26ee8af..326bfdd1cf 100644 --- a/public/language/de/admin/advanced/errors.json +++ b/public/language/de/admin/advanced/errors.json @@ -6,7 +6,7 @@ "manage-error-log": "Fehlerprotokoll verwalten", "export-error-log": "Exportiere das Fehlerprotokoll (CSV)", "clear-error-log": "Fehlerprotokoll leeren", - "route": "Zielroute", + "route": "Pfad", "count": "Anzahl", "no-routes-not-found": "Hurra! Keine 404 Fehler!", "clear404-confirm": "Bist du dir sicher, dass du das 404 Fehlerprotokoll löschen möchtest?", diff --git a/public/language/de/admin/appearance/customise.json b/public/language/de/admin/appearance/customise.json index 029a9de35b..426750609f 100644 --- a/public/language/de/admin/appearance/customise.json +++ b/public/language/de/admin/appearance/customise.json @@ -3,7 +3,7 @@ "custom-css.description": "Füge hier deine eigenen CSS-Eigenschaften ein, sie werden als letztes angewendet.", "custom-css.enable": "Benutzerdefiniertes CSS aktivieren", - "custom-header": "Benutzerdefinierter Kopfbereich", + "custom-header": "Benutzerdefinierter Header", "custom-header.description": "Füge hier dein benutzerdefiniertes HTML (z.B. Javascript, Meta Tags, usw.) ein, welches in den <head> Tag eingefügt werden soll.", - "custom-header.enable": "Benutzerdefinierten Kopfbereich aktivieren" + "custom-header.enable": "Benutzerdefinierten Header aktivieren" } \ No newline at end of file diff --git a/public/language/de/admin/appearance/skins.json b/public/language/de/admin/appearance/skins.json index 960c3196a7..849f5bc704 100644 --- a/public/language/de/admin/appearance/skins.json +++ b/public/language/de/admin/appearance/skins.json @@ -1,9 +1,9 @@ { - "loading": "Lade Aussehen...", + "loading": "Lade Skins...", "homepage": "Homepage", - "select-skin": "Aussehen auswählen", - "current-skin": "Aktuelles Aussehen", - "skin-updated": "Aussehen aktualisiert", - "applied-success": "Aussehen %1 wurde erfolgreich angewendet", - "revert-success": "Aussehen auf Basisfarben zurückgestellt." + "select-skin": "Skin auswählen", + "current-skin": "Aktueller Skin", + "skin-updated": "Skin aktualisiert", + "applied-success": "Skin %1 wurde erfolgreich angewendet", + "revert-success": "Skin auf Basisfarben zurückgestellt." } \ No newline at end of file diff --git a/public/language/de/admin/appearance/themes.json b/public/language/de/admin/appearance/themes.json index 2dede042b2..8eca7c6390 100644 --- a/public/language/de/admin/appearance/themes.json +++ b/public/language/de/admin/appearance/themes.json @@ -1,11 +1,11 @@ { - "checking-for-installed": "Prüfe auf installierte Designs...", + "checking-for-installed": "Prüfe auf installierte Themes...", "homepage": "Homepage", - "select-theme": "Wähle Design", - "current-theme": "Aktuelles Design", - "no-themes": "Keine installierten Designs gefunden.", - "revert-confirm": "Bist du dir sicher, dass du das standard NodeBB Design wieder herstellen willst?", - "theme-changed": "Design geändert", - "revert-success": "Du hast dein NodeBB erfolgreich wieder auf das Standarddesign zurückgesetzt.", + "select-theme": "Wähle Theme", + "current-theme": "Aktuelles Theme", + "no-themes": "Keine installierten Theme gefunden.", + "revert-confirm": "Bist du dir sicher, dass du das standard NodeBB Theme wieder herstellen willst?", + "theme-changed": "Theme geändert", + "revert-success": "Du hast dein NodeBB erfolgreich wieder auf das Standard-Theme zurückgesetzt.", "restart-to-activate": "Bitte starte dein NodeBB neu um das Design voll zu aktivieren." } \ No newline at end of file diff --git a/public/language/de/admin/development/info.json b/public/language/de/admin/development/info.json index 601de896a5..810511ae04 100644 --- a/public/language/de/admin/development/info.json +++ b/public/language/de/admin/development/info.json @@ -6,7 +6,7 @@ "online": "Online", "git": "git", "load": "Auslastung", - "uptime": "Online Zeit", + "uptime": "Uptime", "registered": "Registriert", "sockets": "Sockets", diff --git a/public/language/de/admin/extend/plugins.json b/public/language/de/admin/extend/plugins.json index 355b20b0cd..fd67906de6 100644 --- a/public/language/de/admin/extend/plugins.json +++ b/public/language/de/admin/extend/plugins.json @@ -14,10 +14,10 @@ "dev-interested": "Daran interessiert selbst Plugins für NodeBB zu schreiben?", "docs-info": "Die komplette Dokumentation zur erstellung von Plugins kann im NodeBB Dokumentations Portal gefunden werden.", - "order.description": "Bestimmte Plugins funktionieren ideal, wenn diese for/nach anderen Plugins initialisiert werden.", - "order.explanation": "Die Plugins werden in der hier spezifizierten Reihenfolge geladen, von Oben nach Unten", + "order.description": "Bestimmte Plugins funktionieren optimal, wenn diese vor/nach anderen Plugins initialisiert werden.", + "order.explanation": "Die Plugins werden in der hier spezifizierten Reihenfolge geladen, von oben nach unten", - "plugin-item.themes": "Designs", + "plugin-item.themes": "Themes", "plugin-item.deactivate": "Deaktivieren", "plugin-item.activate": "Aktivieren", "plugin-item.install": "Installieren", @@ -28,20 +28,20 @@ "plugin-item.upgrade": "Aktualisieren", "plugin-item.more-info": "Für weitere Informationen:", "plugin-item.unknown": "Unbekannt", - "plugin-item.unknown-explanation": "Der Status dieses Plugins konnte nicht bestimmt werden, möglicherweise aufgrund eines Fehlkonfigurationsfehlers.", + "plugin-item.unknown-explanation": "Der Status dieses Plugins konnte nicht bestimmt werden, möglicherweise aufgrund eines Konfigurationsfehlers.", "alert.enabled": "Plugin aktiviert", "alert.disabled": "Plugin deaktiviert", "alert.upgraded": "Plugin aktualisiert", "alert.installed": "Plugin installiert", "alert.uninstalled": "Plugin deinstalliert", - "alert.activate-success": "Bitte starten Sie ihr NodeBB neu, um dieses Plugin vollständig zu aktivieren", + "alert.activate-success": "Bitte starte NodeBB neu, um dieses Plugin vollständig zu aktivieren", "alert.deactivate-success": "Plugin erfolgreich deaktiviert", - "alert.upgrade-success": "Bitte laden Sie ihr NodeBB neu um dieses Plugin vollständig zu aktualisieren", - "alert.install-success": "Plugin erfolgreich installiert. Bitte aktivieren Sie das Plugin", + "alert.upgrade-success": "Bitte lade NodeBB neu, um dieses Plugin vollständig zu aktualisieren", + "alert.install-success": "Plugin erfolgreich installiert. Bitte aktiviere das Plugin", "alert.uninstall-success": "Das Plugin wurde erfolgreich deaktiviert und deinstalliert.", - "alert.suggest-error": "

NodeBB konnte den Paket-Manager nicht erreichen, wollen Sie mit der Installation der neuesten Version fortfahren

Der Server meldete (%1): %2
", - "alert.package-manager-unreachable": "

NodeBB konnte den Paket-manager nicht erreichen, eine aktualisierung wird momentan nicht empfohlen.

", - "alert.incompatible": "

Ihre NodeBB Version (v%1) ist nur für aktualisierungen bis v%2 dieses Plugins bestimmt. Bitte aktualisieren Sie NodeBB wenn Sie eine neuere Version dieses plugins installieren wollen.

", + "alert.suggest-error": "

NodeBB konnte den Paket-Manager nicht erreichen. Willst Du mit der Installation der neuesten Version fortfahren

Der Server meldete (%1): %2
", + "alert.package-manager-unreachable": "

NodeBB konnte den Paket-Manager nicht erreichen, eine Aktualisierung wird momentan nicht empfohlen.

", + "alert.incompatible": "

NodeBB Version (v%1) ist nur für Aktualisierungen bis v%2 dieses Plugins bestimmt. Bitte aktualisiere NodeBB, wenn eine neuere Version dieses Plugins installiert werden soll.

", "alert.possibly-incompatible": "

Keine Kompatibilitätsinformationen gefunden

Dieses Plugin legte keine spezifische NodeBB version fest, welche für die Installation benötigt wird. Volle Kompatibilität kann nicht gewährleistet werden, was dazu führen könnte, dass ihr NodeBB nicht mehr korrekt startet.

Für den Fall, dass NodeBB nicht mehr ordnungsgemäß startet:

$ ./nodebb reset plugin=\"%1\"

Soll mit der installation der neuesten Version dieses Plugins fortgefahren werden?

" } diff --git a/public/language/de/admin/extend/rewards.json b/public/language/de/admin/extend/rewards.json index 5d4e5cb3da..38c39fcf44 100644 --- a/public/language/de/admin/extend/rewards.json +++ b/public/language/de/admin/extend/rewards.json @@ -4,7 +4,7 @@ "condition-is": "Ist:", "condition-then": "Dann:", "max-claims": "Anzahl der male, die diese Belohnung beansprucht werden kann", - "zero-infinite": "Geben sie 0 für unendlich ein", + "zero-infinite": "Gib 0 für unendlich ein", "delete": "Entfernen", "enable": "Aktivieren", "disable": "Deaktivieren", diff --git a/public/language/de/admin/extend/widgets.json b/public/language/de/admin/extend/widgets.json index 73308e9c16..b633981898 100644 --- a/public/language/de/admin/extend/widgets.json +++ b/public/language/de/admin/extend/widgets.json @@ -1,14 +1,14 @@ { "available": "Verfügbare Widgets", - "explanation": "Wählen Sie ein Widget vom Dropdown-Menu aus und ziehen Sie es anschließend links in den Widget-Bereich einer Vorlage.", - "none-installed": "Keine Widgets gefunden! Aktivieren Sie das \"essential widgets\"-Plugin in den Plugin-Einstellungen.", + "explanation": "Widget vom Dropdown-Menu auswählen und anschließend links in den Widget-Bereich einer Vorlage ziehen.", + "none-installed": "Keine Widgets gefunden! Aktiviere das \"Essential Widgets\"-Plugin in den Plugin-Einstellungen.", "containers.available": "Verfügbare Container", - "containers.explanation": "Ziehen Sie sie auf ein beliebiges aktives Widget", + "containers.explanation": "Auf ein beliebiges aktives Widget ziehen", "containers.none": "Nichts", - "container.well": "Brunnen", + "container.well": "Well", "container.jumbotron": "Jumbotron", "container.panel": "Panel", - "container.panel-header": "Panel Kopfbereich", + "container.panel-header": "Panel Header", "container.panel-body": "Panel Körper", "container.alert": "Alarm", diff --git a/public/language/de/admin/general/dashboard.json b/public/language/de/admin/general/dashboard.json index f0ae877ce3..29772ca3e6 100644 --- a/public/language/de/admin/general/dashboard.json +++ b/public/language/de/admin/general/dashboard.json @@ -17,18 +17,18 @@ "updates": "Updates", "running-version": "Es läuft NodeBB v%1.", "keep-updated": "Stelle sicher, dass dein NodeBB immer auf dem neuesten Stand für die neuesten Sicherheits-Patches und Bug-fixes ist.", - "up-to-date": "

System ist aktuell

", - "upgrade-available": "

Version (v%1) wurde veröffentlicht. Beachte um ein NodeBB Upgrade durchzuführen.

", - "prerelease-upgrade-available": "

Das ist eine veraltete pre-release Version von NodeBB. Version (v%1) wurde veröffentlicht. Beachte um ein NodeBB Upgrade durchzuführen.

", + "up-to-date": "

NodeBB Version ist aktuell

", + "upgrade-available": "

Version (v%1) wurde veröffentlicht. Es wird ein NodeBB Upgrade empfohlen.

", + "prerelease-upgrade-available": "

Das ist eine veraltete pre-release Version von NodeBB. Version (v%1) wurde veröffentlicht. Es wird ein NodeBB Upgrade empfohlen.

", "prerelease-warning": "

Das ist eine pre-release Version von NodeBB. Es können ungewollte Fehler auftreten.

", - "running-in-development": "Das Forum wurde im Entwicklermodus gestartet. Das Forum könnte potenziellen Gefahren ausgeliefert sein. Bitte kontaktieren Sie Ihren Systemadministrator.", + "running-in-development": "Das Forum wurde im Entwicklermodus gestartet. Das Forum könnte potenziellen Gefahren ausgeliefert sein. Bitte kontaktiere den Systemadministrator.", "notices": "Hinweise", - "restart-not-required": "Neustart nicht benötigt", + "restart-not-required": "Kein Neustart benötigt", "restart-required": "Neustart benötigt", "search-plugin-installed": "Such-Plugin installiert", - "search-plugin-not-installed": "Such-Plugin nicht installiert", - "search-plugin-tooltip": "Installieren Sie ein Such-Plugin auf der Plugin seite um die Such-Funktionalität zu aktivieren", + "search-plugin-not-installed": "Kein Such-Plugin installiert", + "search-plugin-tooltip": "Installiere ein Such-Plugin auf der Plugin-Seite um die Such-Funktionalität zu aktivieren", "control-panel": "Systemsteuerung", "reload": "Reload", diff --git a/public/language/de/admin/general/languages.json b/public/language/de/admin/general/languages.json index ffea3c3f7d..af0bb78d1f 100644 --- a/public/language/de/admin/general/languages.json +++ b/public/language/de/admin/general/languages.json @@ -1,5 +1,5 @@ { "language-settings": "Spracheinstellungen", - "description": "Die Standardsprache legt die Spracheinstellungen für alle Benutzer fest, die das Forum besuchen.
Einzelne Benutzer können die Standardsprache auf der Seite mit den Kontoeinstellungen überschreiben.", + "description": "Die Standardsprache legt die Spracheinstellungen für alle Benutzer fest, die das Forum besuchen.
Einzelne Benutzer können die Standardsprache auf der Seite in ihren Kontoeinstellungen überschreiben.", "default-language": "Standardsprache" } \ No newline at end of file diff --git a/public/language/de/admin/general/social.json b/public/language/de/admin/general/social.json index 5f5d7b28e8..0614aca7da 100644 --- a/public/language/de/admin/general/social.json +++ b/public/language/de/admin/general/social.json @@ -1,5 +1,5 @@ { "post-sharing": "Beiträge teilen", - "info-plugins-additional": "Plugins können zusätzliche Netzwerke für das Teilen von Beiträgen hinzufügen.", + "info-plugins-additional": "Plugins können zusätzliche soziale Netzwerke für das Teilen von Beiträgen hinzufügen.", "save-success": "Erfolgreich gespeichert!" } \ No newline at end of file diff --git a/public/language/de/admin/manage/flags.json b/public/language/de/admin/manage/flags.json deleted file mode 100644 index b215a75901..0000000000 --- a/public/language/de/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Tägliche Meldungen", - "by-user": "Meldungen des Benutzers", - "by-user-search": "Nach gemeldeten Beiträgen anhand des Benutzernamens Suchen", - "category": "Kategorie", - "sort-by": "Sortieren nach:", - "sort-by.most-flags": "Meiste Meldungen", - "sort-by.most-recent": "Neueste", - "search": "Suche", - "dismiss-all": "Alle verwerfen", - "none-flagged": "Keine gemeldeten Beiträge", - "posted-in": "Gepostet am %1", - "read-more": "Mehr Lesen", - "flagged-x-times": "Dieser Beitrag wurde %1 mal gemeldet:", - "dismiss": "Diese Meldung verwerfen", - "delete-post": "Beitrag löschen", - - "alerts.confirm-delete-post": "Sind Sie sicher, dass Sie diesen beitrag löschen wollen?" -} \ No newline at end of file diff --git a/public/language/de/admin/manage/groups.json b/public/language/de/admin/manage/groups.json index 818d721601..fa7244a3e5 100644 --- a/public/language/de/admin/manage/groups.json +++ b/public/language/de/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Gruppenname", "description": "Gruppenbeschreibung", + "member-count": "Mitglieder Anzahl", "system": "System-Gruppe", "edit": "Ändern", "search-placeholder": "Suchen", @@ -9,7 +10,7 @@ "create-button": "Erstellen", "alerts.create-failure": "Oh Oh

Ein Problem ist beim erstellen deiner Gruppe aufgetreten. Bitte versuche es später noch mal!

", - "alerts.confirm-delete": "Sind Sie sicher, dass Sie diese Gruppe löschen wollen?", + "alerts.confirm-delete": "Diese Gruppe wirklich löschen ?", "edit.name": "Name", "edit.description": "Beschreibung", diff --git a/public/language/de/admin/manage/ip-blacklist.json b/public/language/de/admin/manage/ip-blacklist.json index e98633eee8..a6d4f1cff9 100644 --- a/public/language/de/admin/manage/ip-blacklist.json +++ b/public/language/de/admin/manage/ip-blacklist.json @@ -5,7 +5,7 @@ "validate": "Blacklist validieren", "apply": "Blacklist anwenden", "hints": "Syntax Hinweise", - "hint-1": "Fügen Sie einzelne IP-Adresses pro Zeile ein. Sie können IP-Blöcke hinzufügen, so lange diese im CIDR Format (z.b. 192.168.100.0/22) eingegeben werden.", + "hint-1": "Pro Zeile kann eine IP-Adresse angegeben werden. Es können auch IP-Blöcke im CIDR Format (z.B. 192.168.100.0/22) hinzugefügt werden.", "hint-2": "Sie können Kommentare hinzufügen, indem Sie die Zeilen mit dem # Symbol beginnen.", "validate.x-valid": "%1 von %2 Regel(n) zulässig.", diff --git a/public/language/de/admin/manage/registration.json b/public/language/de/admin/manage/registration.json index f8e1f5f4ad..1971466302 100644 --- a/public/language/de/admin/manage/registration.json +++ b/public/language/de/admin/manage/registration.json @@ -1,9 +1,9 @@ { - "queue": "Schlange", - "description": "Es sind keine Benutzer in der Registrierungsschlange.
Um diese Funktion zu aktivieren, gehen SIe zur Einstellungen → Benutzer → Benutzer registrierung und ändern sie Registrierungsart auf \"Admin Genehmigung\".", + "queue": "Warteschlange", + "description": "Es sind keine Benutzer in der Registrierungs-Warteschlange.
Um diese Funktion zu aktivieren, gehe zu Einstellungen → Benutzer → Benutzer erstellen und ändern sie Registrierungsart auf \"Admin Genehmigung\".", "list.name": "Name", - "list.email": "Email", + "list.email": "E-Mail", "list.ip": "IP-Adresse", "list.time": "Zeit", "list.username-spam": "Häufigkeit: %1 Erscheint: %2 Sicherheit: %3", @@ -11,7 +11,7 @@ "list.ip-spam": "Häufigkeit: %1 Erscheint: %2", "invitations": "Einladungen", - "invitations.description": "Unterhalb ist eine komplette Liste der versandten Einladungen. Benutze Strg+F um die Liste per Email oder Nutzername zu durchsuchen.

Der Nutzername wird für die Nutzer die ihre einladung angenommen haben Rechts von den Emails angezeigt.", + "invitations.description": "Unterhalb ist eine komplette Liste der versandten Einladungen. Benutze Strg+F um die Liste per Email oder Nutzername zu durchsuchen.

Der Nutzername wird für die Nutzer die ihre Einladung angenommen haben rechts von den E-Mails angezeigt.", "invitations.inviter-username": "Nutzername des Einladenden", "invitations.invitee-email": "Email des eingeladenen", "invitations.invitee-username": "Nutzername des eingeladenen (Wenn registriert)", diff --git a/public/language/de/admin/manage/tags.json b/public/language/de/admin/manage/tags.json index d9230c2253..be8bdd9650 100644 --- a/public/language/de/admin/manage/tags.json +++ b/public/language/de/admin/manage/tags.json @@ -1,14 +1,14 @@ { - "none": "Ihr Form hat bisher noch keine Themen mit Tags.", + "none": "Das Forum hat bisher noch keine Themen mit Tags.", "bg-color": "Hintergrundfarbe", "text-color": "Textfarbe", "create-modify": "Tags erstellen & bearbeiten", - "description": "Wählen sie Tags aus indem sie klicken und/oder ziehen, drücken die Shift um mehrere auszuwählen", + "description": "Tags auswählen indem Du klickst und/oder ziehst, drücke die SHIFT-Taste, um mehrere auszuwählen", "create": "Tag erstellen", "modify": "Tag bearbeiten", "delete": "Ausgewählte Tags entfernen", "search": "Nach Tags suchen", - "settings": "Klicken Sie hier um die Tag-Einstellungsseite zu öffnen.", + "settings": "Klicke hier, um die Tag-Einstellungsseite zu öffnen.", "name": "Tagname", "alerts.editing-multiple": "Bearbeite mehrere Tags", diff --git a/public/language/de/admin/manage/users.json b/public/language/de/admin/manage/users.json index 277b0a200e..c1f24ae8fe 100644 --- a/public/language/de/admin/manage/users.json +++ b/public/language/de/admin/manage/users.json @@ -3,9 +3,9 @@ "edit": "Bearbeiten", "make-admin": "Zum Administrator befördern", "remove-admin": "Adminstatus entfernen", - "validate-email": "Email bestätigen", - "send-validation-email": "Bestätigungsemail senden", - "password-reset-email": "Passwortreset email senden", + "validate-email": "E-Mail bestätigen", + "send-validation-email": "Bestätigungs E-Mail senden", + "password-reset-email": "Passwort-Reset E-Mail senden", "ban": "Benutzer verbannen", "temp-ban": "Benutzer temporär verbannen", "unban": "Benutzer entbannen", @@ -23,16 +23,16 @@ "pills.top-posters": "Top Poster", "pills.top-rep": "Größtes Ansehen", "pills.inactive": "Inaktiv", - "pills.flagged": "Meist gemeldetster", + "pills.flagged": "Meist gemeldete", "pills.banned": "Gebannt", "pills.search": "Benutzer Suche", "search.username": "Nach Nutzernamen", - "search.username-placeholder": "Geben Sie einen Nutzernamen ein um danach zu suchen", - "search.email": "Nach Email", - "search.email-placeholder": "Geben Sie eine Email Adresse ein um danach zu suchen", + "search.username-placeholder": "Einen Nutzernamen eingeben, um danach zu suchen", + "search.email": "Nach E-Mail", + "search.email-placeholder": "Eine E-Mail Adresse eingeben, um danach zu suchen", "search.ip": "Nach IP-Adresse", - "search.ip-placeholder": "Geben Sie eine IP Adresse ein um danach zu suchen", + "search.ip-placeholder": "IP Adresse eingeben, um danach zu suchen", "search.not-found": "Benutzer nicht gefunden!", "inactive.3-months": "3 Monate", @@ -41,7 +41,7 @@ "users.uid": "UID", "users.username": "Nutzername", - "users.email": "Email", + "users.email": "E-Mail", "users.postcount": "Anzahl der Beiträge", "users.reputation": "Ansehen", "users.flags": "Meldungen", @@ -50,19 +50,19 @@ "users.banned": "Gebannt", "create.username": "Benutzername", - "create.email": "Email", - "create.email-placeholder": "Email dieses Benutzers", + "create.email": "E-Mail", + "create.email-placeholder": "E-Mail dieses Benutzers", "create.password": "Passwort", "create.password-confirm": "Passwort bestätigen", "temp-ban.length": "Banndauer", - "temp-ban.reason": "Grund (Optional)", + "temp-ban.reason": "Grund (optional)", "temp-ban.hours": "Stunden", "temp-ban.days": "Tage", - "temp-ban.explanation": "Geben Sie die dauer des Bans an. Beachten Sie, dass eine Zeit von 0 als permanent interpretiert wird.", + "temp-ban.explanation": "Geben die dauer des Bans an. Beachte, dass eine Zeit von 0 als permanent interpretiert wird.", - "alerts.confirm-ban": "Wollen Sie diesen Nutzer wirklich permanent bannen?", - "alerts.confirm-ban-multi": "Wollen Sie diese Nutzer wirklich permanent bannen?", + "alerts.confirm-ban": "Möchtest Du diesen Nutzer wirklich permanent bannen?", + "alerts.confirm-ban-multi": "Möchtest Du diese Nutzer wirklich permanent bannen?", "alerts.ban-success": "Benutzer gebannt!", "alerts.button-ban-x": "%1 Nutzer bannen", "alerts.unban-success": "Benutzer entbannt!", @@ -72,12 +72,12 @@ "alerts.make-admin-success": "Die Benutzer sind nun Administratoren.", "alerts.confirm-remove-admin": "Möchtest du wirklich Admins entfernen?", "alerts.remove-admin-success": "Diese(r) Nutzer sind/ist kein(e) Administrator(en) mehr ", - "alerts.confirm-validate-email": "Möchten Sie wirklich die Emails dieser benutzer/dieses benutzers bestätigen?", - "alerts.validate-email-success": "Emails bestätigt", - "alerts.password-reset-confirm": "Möchten Sie wirklich (eine) Passwort-Reset-Email(s) an diese(n) Benutzer schicken?", - "alerts.confirm-delete": "Warnung!
Wollen Sie wirklich diese(n) Benutzer löschen?
Diese Aktion kann nicht rückgängig gemacht werden! Nur der Account wird dabei gelöscht. Deren Themen und Beiträge bleiben dabei erhalten.", + "alerts.confirm-validate-email": "Möchtest Du wirklich die E-Mails dieser Benutzer/dieses Benutzers bestätigen?", + "alerts.validate-email-success": "E-Mails bestätigt", + "alerts.password-reset-confirm": "Möchtest Du wirklich (eine) Passwort-Reset-Email(s) an diese(n) Benutzer schicken?", + "alerts.confirm-delete": "Warnung!
Möchtest Du wirklich diese(n) Benutzer löschen?
Diese Aktion kann nicht rückgängig gemacht werden! Nur der Account wird dabei gelöscht. Deren Themen und Beiträge bleiben dabei erhalten.", "alerts.delete-success": "Benutzer gelöscht!", - "alerts.confirm-purge": "Warnung!
Sind Sie sicher, dass Sie diese Nutzer und deren Beiträge löschen wollen?
Diese Aktion kann nicht rückgängig gemacht werden! Alle Nutzerdaten und Beiträge werden dabei gelöscht!", + "alerts.confirm-purge": "Warnung!
Bist Du sicher, dass Du diese Nutzer und deren Beiträge löschen willst?
Diese Aktion kann nicht rückgängig gemacht werden! Alle Nutzerdaten und Beiträge werden dabei gelöscht!", "alerts.create": "Nutzer Erstellen", "alerts.button-create": "Erstellen", "alerts.button-cancel": "Abbrechen", @@ -85,7 +85,7 @@ "alerts.error-x": "Fehler

%1

", "alerts.create-success": "Nutzer erstellt", - "alerts.prompt-email": "Email:", + "alerts.prompt-email": "E-Mail:", "alerts.email-sent-to": "Eine Einladungsemail wurde an %1 gesendet", "alerts.x-users-found": "%1 Nutzer gefunden! Die Suche dauerte %2ms." } \ No newline at end of file diff --git a/public/language/de/admin/settings/advanced.json b/public/language/de/admin/settings/advanced.json index 29a3d7c701..87c5b2ca21 100644 --- a/public/language/de/admin/settings/advanced.json +++ b/public/language/de/admin/settings/advanced.json @@ -6,11 +6,11 @@ "headers.allow-from": "ALLOW-FROM setzen um NodeBB in einem iFrame zu platzieren", "headers.powered-by": "Anpassen des \"Powered By\" Headers von NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "Um den Zugriff auf alle Seiten zu blockieren, leer lassen oder auf null setzen", + "headers.acao-help": "Um den Zugriff zu allen Seiten zu verbieten, leer lassen.", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", - "traffic.help": "NodeBB wird mit einem module geliefert, welches automatisch anfragen in \"High-Traffic\" situationen blockiert. Sie können diese Einstellungen hier Fine-Tunen, auch wenn die Standardeinstellungen einen guten Anfang darstellen sollten", + "traffic.help": "NodeBB wird mit einem Modul geliefert, welches automatisch anfragen in High-Traffic Situationen blockiert. Du kannst diese Einstellungen hier ändern, auch wenn die Standardeinstellungen einen guten Anfang darstellen sollten", "traffic.enable": "Traffic Management aktivieren", "traffic.event-lag": "Eventschleifenverzögerungsschwelle (in Millisekunden)", "traffic.event-lag-help": "Das Heruntersetzen dieses Werts reduziert die Ladezeiten, aber wird auch dafür sorgen, dass die \"Übermäßige Belastung\" nachricht öfter angezeigt wird. (Neustart erforderlich)", diff --git a/public/language/de/admin/settings/cookies.json b/public/language/de/admin/settings/cookies.json index 6d818fd893..0e87e306d3 100644 --- a/public/language/de/admin/settings/cookies.json +++ b/public/language/de/admin/settings/cookies.json @@ -1,5 +1,5 @@ { - "eu-consent": "EU-Konsens", + "eu-consent": "EU Cookie Zustimmung", "consent.enabled": "Aktiviert", "consent.message": "Benachrichtigung", "consent.acceptance": "Akzeptierungsnachricht", diff --git a/public/language/de/admin/settings/email.json b/public/language/de/admin/settings/email.json index 83b3683e91..da2109f9df 100644 --- a/public/language/de/admin/settings/email.json +++ b/public/language/de/admin/settings/email.json @@ -1,25 +1,25 @@ { - "email-settings": "Email-Einstellungen", + "email-settings": "E-Mail Einstellungen", "address": "E-Mail Adresse", - "address-help": "Die folgende Email-Adresse ist die Emai-Adresse, welche dem Empfänger im \"Von\" und \"Antworten\" Bereich sehen wird.", + "address-help": "Die folgende E-Mail Adresse ist die E-Mail Adresse, welche dem Empfänger im \"Von\" und \"Antworten\" Bereich sehen wird.", "from": "Name des Absenders", - "from-help": "Der Name des Absenders, welcher in der Email angezeigt werden soll.", + "from-help": "Der Name des Absenders, welcher in der E-Mail angezeigt werden soll.", "gmail-routing": "Gmail Routing", "gmail-routing-help1": "Es gab Berichte bezüglich des \"Gmail Routing\", welches nicht auf Accounts mit erhöhten Sicherheitseinstellungen funktionierte (Standardeinstellung). In diesem Fall müssen sie ihren Gmail Account konfigurieren weniger sichere Apps zu erlauben.", "gmail-routing-help2": "Für mehr informationen zu diesem Workaround, konsultieren Sie bitte diesen NodeMailer Artikel über dieses Problem. Eine alternative wäre ein Plugin von Drittherstellern wie SendGrid, mailgun etc. zu verwenden. Verfügbare Plugins durchsuchen.", - "gmail-transport": "Emails über einen Gmail/Google Apps account verschicken", + "gmail-transport": "E-Mails über einen Gmail/Google Apps Account verschicken", "gmail-transport.username": "Benutzername", - "gmail-transport.username-help": "Geben Sie die volle Email-Adresse hier ein, insbesondere wenn Sie eine Google-Apps verwaltete domain verwenden.", + "gmail-transport.username-help": "Gib die vollständige E-Mail Adresse hier ein, insbesondere wenn Du eine Google-Apps verwaltete Domain verwendest.", "gmail-transport.password": "Passwort", - "template": "Email Vorlage bearbeiten", - "template.select": "Email Vorlage auswählen", + "template": "E-Mail Vorlage bearbeiten", + "template.select": "E-Mail Vorlage auswählen", "template.revert": "Original wiederherstellen", - "testing": "Emailtests", - "testing.select": "Wählen Sie die Email Vorlage", - "testing.send": "Test-Email versenden", - "testing.send-help": "Die Test-Email wird an die Email des momentan eingeloggten Nutzers geschickt.", - "subscriptions": "Email Abonnements", - "subscriptions.disable": "Email-Benachrichtigungsmails deaktivieren", + "testing": "E-Mail Test", + "testing.select": "Wählen Sie die E-Mail Vorlage", + "testing.send": "Test-E-Mail versenden", + "testing.send-help": "Die Test-E-Mail wird an die E-Mail Adresse des momentan eingeloggten Nutzers geschickt.", + "subscriptions": "E-Mail Abonnements", + "subscriptions.disable": "E-Mail-Benachrichtigung deaktivieren", "subscriptions.hour": "Sende Zeit", "subscriptions.hour-help": "Bitte geben Sie eine Nummer ein, welche die Stunde repräsentiert zu welcher geplante Emails versandt werden sollen (z.B. 0 für Mitternacht, 17 für 5 Uhr Nachmittags). Beachten Sie, dass die Zeit auf der Serverzeit basiert und daher nicht umbedingt mit ihrer Systemzeit übereinstimmen muss.
Die ungefähre Serverzeit ist:
Die nächste tägliche Sendung ist um geplant" } \ No newline at end of file diff --git a/public/language/de/admin/settings/general.json b/public/language/de/admin/settings/general.json index ca83119258..014843ced2 100644 --- a/public/language/de/admin/settings/general.json +++ b/public/language/de/admin/settings/general.json @@ -1,25 +1,25 @@ { - "site-settings": "Seiteneinstellungen", - "title": "Seiten Titel", - "title.name": "Ihr Community Name", - "title.show-in-header": "Seitentitel im Header anzeigen", + "site-settings": "Forum Einstellungen", + "title": "Forum Titel", + "title.name": "Name Deiner Community", + "title.show-in-header": "Titel im Header anzeigen", "browser-title": "Browser Titel", - "browser-title-help": "Wenn kein Browser-Titel spezifiziert wurde, wird der Seitentitel verwendet", + "browser-title-help": "Wenn kein Browser Titel spezifiziert wurde, wird der Forum Titel verwendet", "title-layout": "Titel Layout", - "title-layout-help": "Definieren Sie, wie der Browser Titel Strukturiert wird d.h.z.B. {pageTitle} | {browserTitle}", - "description.placeholder": "Eine kurze Beschreibung ihrer Community", - "description": "Seitenbeschreibung", - "keywords": "Seiten-Schlüsselworte", - "keywords-placeholder": "Schlüsselworte, die ihre Community beschreiben, mit komma getrennt", - "logo": "Seiten-Logo", + "title-layout-help": "Definiert wie der Browser Titel gebildet wird, z.B. {pageTitle} | {browserTitle}", + "description.placeholder": "Eine kurze Beschreibung der Community", + "description": "Forum Beschreibung", + "keywords": "Forum Schlüsselworte", + "keywords-placeholder": "Schlüsselworte, die ihre Community beschreiben, mit Komma getrennt", + "logo": "Forum Logo", "logo.image": "Bild", - "logo.image-placeholder": "Pfad zu einem Logo, welches im Kopfbereich des Forums angezeigt werden soll", + "logo.image-placeholder": "Pfad zu einem Logo, welches im Header des Forums angezeigt werden soll", "logo.upload": "Hochladen", "logo.url": "URL", - "logo.url-placeholder": "Die URL des Seiten-Logos", + "logo.url-placeholder": "Die URL des Logos", "logo.url-help": "Wenn das Logo angeklickt wird, wird der Nutzer an diese Adresse weitergeleitet. Wenn das Feld leer gelassen wird, wird der Nutzer zur Startseite geleitet.", - "logo.alt-text": "Alternativer Text", - "log.alt-text-placeholder": "Alternativer text, falls das Bild nicht angezeigt werden kann", + "logo.alt-text": "Alt Text", + "log.alt-text-placeholder": "Alternativer Text, falls das Bild nicht angezeigt werden kann", "favicon": "Favicon", "favicon.upload": "Hochladen", "touch-icon": "Homescreen/Touch Icon", diff --git a/public/language/de/admin/settings/group.json b/public/language/de/admin/settings/group.json index bce69d2f53..99f4b5ea31 100644 --- a/public/language/de/admin/settings/group.json +++ b/public/language/de/admin/settings/group.json @@ -1,8 +1,8 @@ { "general": "Allgemein", "private-groups": "Private Gruppen", - "private-groups.help": "Wenn aktiviert, benötigt das beitreten einer Gruppe die Bestätigung des jeweiligen Besitzers(Standard: aktiviert)", - "private-groups.warning": "Vorsicht! Wenn diese Option deaktiviert ist, und Sie private Gruppen haben, werden diese automatisch Öffentlich.", + "private-groups.help": "Wenn aktiviert, erfordert das Beitreten einer Gruppe die Bestätigung des jeweiligen Besitzers(Standard: aktiviert)", + "private-groups.warning": "Vorsicht! Wenn diese Option deaktiviert ist, und es private Gruppen gibt, werden diese automatisch öffentlich.", "allow-creation": "Erstellung von Gruppen erlauben", "allow-creation-help": "Wenn aktiviert können Nutzer Gruppen erstellen (Standard: deaktiviert)", "max-name-length": "Maximale Länge von Gruppennamen", diff --git a/public/language/de/admin/settings/guest.json b/public/language/de/admin/settings/guest.json index b515580c87..d3189508fd 100644 --- a/public/language/de/admin/settings/guest.json +++ b/public/language/de/admin/settings/guest.json @@ -1,7 +1,7 @@ { "handles": "Gastzugang", "handles.enabled": "Gastzugänge erlauben", - "handles.enabled-help": "Diese option offenbart ein neues Feld, welches Gästen erlaubt einen Nutzernamen zu wählen, welcher Sie mit jedem Beitrag assoziiert den sie erstellen. Wenn diese option deaktiviert ist, werden sie einfach \"Gast\" genannt", + "handles.enabled-help": "Diese Option offenbart ein neues Feld, welches Gästen erlaubt einen Nutzernamen zu wählen, welcher sie mit jedem Beitrag assoziiert den sie erstellen. Wenn diese Option deaktiviert ist, werden sie einfach \"Gast\" genannt", "privileges": "Gast-Berechtigungen", "privileges.can-search": "Gästen erlauben das Forum zu durchsuchen ohne eingeloggt zu sein", "privileges.can-search-users": "Gästen erlauben nach Benutzern zu suchen ohne eingeloggt zu sein" diff --git a/public/language/de/admin/settings/notifications.json b/public/language/de/admin/settings/notifications.json index 38a30ccbb1..3aab343267 100644 --- a/public/language/de/admin/settings/notifications.json +++ b/public/language/de/admin/settings/notifications.json @@ -1,5 +1,5 @@ { "notifications": "Benachrichtigungen", "welcome-notification": "Wilkommensnachricht", - "welcome-notification-link": "Wilkommensnachrichtslink" + "welcome-notification-link": "Wilkommensnachricht-Link" } \ No newline at end of file diff --git a/public/language/de/admin/settings/pagination.json b/public/language/de/admin/settings/pagination.json index 33bed37f3f..cf2e6456f1 100644 --- a/public/language/de/admin/settings/pagination.json +++ b/public/language/de/admin/settings/pagination.json @@ -1,9 +1,9 @@ { - "pagination": "Seitennummerierungseinstellungen", - "enable": "Themen in Seiten einteilen anstatt unendlich weit zu scrollen", + "pagination": "Seitennummerierungs Einstellungen", + "enable": "Themen in Seiten einteilen anstatt endlos zu scrollen", "topics": "Themen Seitennummerierung", "posts-per-page": "Beiträge pro Seite", "categories": "Kategorie Seitennummerierung", "topics-per-page": "Themen pro Seite", - "initial-num-load": "Ursprüngliche anzahl an Themen, die bei Ungelesen, Aktuell und beliebt geladen werden sollen" + "initial-num-load": "Ursprüngliche Anzahl an Themen, die bei ungelesen, aktuell und beliebt geladen werden sollen" } \ No newline at end of file diff --git a/public/language/de/admin/settings/post.json b/public/language/de/admin/settings/post.json index 3c4d0b97d6..5844dfe9eb 100644 --- a/public/language/de/admin/settings/post.json +++ b/public/language/de/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Ungelesen-Einstellungen", "unread.cutoff": "Ungelesen-Limit (in Tagen)", "unread.min-track-last": "Minimale Anzahl an Beiträgen pro Thema bevor die letzte Sichtung mitgeschrieben wird", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signatureinstellungen", "signature.disable": "Signaturen deaktivieren", "signature.no-links": "Links in signaturen deaktivieren", diff --git a/public/language/de/admin/settings/sockets.json b/public/language/de/admin/settings/sockets.json index 2fa6475bef..d86c0b2d3a 100644 --- a/public/language/de/admin/settings/sockets.json +++ b/public/language/de/admin/settings/sockets.json @@ -1,6 +1,6 @@ { - "reconnection": "Neuverbindungseinstellungen", - "max-attempts": "Maximale Anzahl von Neuverbindungsversuchen", + "reconnection": "Reconnection Einstellungen", + "max-attempts": "Maximale Anzahl von Reconnection-Versuchen", "default-placeholder": "Standard: %1", - "delay": "Neuverbindungsverzögerung" + "delay": "Reconnection-Verzögerung" } \ No newline at end of file diff --git a/public/language/de/admin/settings/user.json b/public/language/de/admin/settings/user.json index c0309d47fb..b5bfb291c9 100644 --- a/public/language/de/admin/settings/user.json +++ b/public/language/de/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimale länge des Benutzernamens", "max-username-length": "Maximale länge des Benutzernamens", "min-password-length": "Minimale länge des Passwortes", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximale länge von Über Mich", "terms-of-use": "Forum Nutzungsbedingungen (Leer lassen um es zu deaktivieren)", "user-search": "Benutzersuche", diff --git a/public/language/de/admin/settings/web-crawler.json b/public/language/de/admin/settings/web-crawler.json index c5808d268e..5ef1848372 100644 --- a/public/language/de/admin/settings/web-crawler.json +++ b/public/language/de/admin/settings/web-crawler.json @@ -1,10 +1,10 @@ { "crawlability-settings": "Crawlability Einstellung", - "robots-txt": "Benutzerdefinierte Robots.txtLeer lassen für Standardeinstellung", - "sitemap-feed-settings": "Seitenübersicht & Feed Einstellungen", + "robots-txt": "Benutzerdefinierte robots.txt Leer lassen für Standardeinstellung", + "sitemap-feed-settings": "Sitemap & Feed Einstellungen", "disable-rss-feeds": "Deaktiviere RSS Feeds", - "disable-sitemap-xml": "Deaktiviere Seitenübersicht.xml", - "sitemap-topics": "Anzahl der Themen die auf der Seitenübersicht angezeigt werden", - "clear-sitemap-cache": "Leere Seitenübersicht Cache", - "view-sitemap": "Zeige Seitenübersicht" + "disable-sitemap-xml": "Deaktiviere sitemap.xml", + "sitemap-topics": "Anzahl der Themen die auf der Sitemap angezeigt werden", + "clear-sitemap-cache": "Sitemap Cache leeren", + "view-sitemap": "Zeige Sitemap" } \ No newline at end of file diff --git a/public/language/de/email.json b/public/language/de/email.json index 7ba24003a7..1ade727750 100644 --- a/public/language/de/email.json +++ b/public/language/de/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Diese Mitteilung wurde dir aufgrund deiner Abonnement-Einstellungen gesendet.", "test.text1": "Dies ist eine Test-E-Mail, um zu überprüfen, ob der E-Mailer deines NodeBB korrekt eingestellt wurde.", "unsub.cta": "Klicke hier, um diese Einstellungen zu ändern.", + "banned.subject": "Du wurdest von %1 gebannt.", + "banned.text1": "Der Benutzer %1 wurde von %2 gebannt.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Danke!" } \ No newline at end of file diff --git a/public/language/de/error.json b/public/language/de/error.json index 105b48a3a8..51526a9041 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -30,6 +30,7 @@ "password-too-long": "Passwort ist zu lang", "user-banned": "Benutzer ist gesperrt", "user-banned-reason": "Entschuldige, dieses Konto wurde gebannt (Grund: %1)", + "user-banned-reason-until": "Entschuldigung, dieser Account wurde bis %1 (Reason: %2) gebannt.", "user-too-new": "Entschuldigung, du musst %1 Sekunde(n) warten, bevor du deinen ersten Beitrag schreiben kannst.", "blacklisted-ip": "Deine IP-Adresse ist für diese Plattform gesperrt. Sollte dies ein Irrtum sein, dann kontaktiere bitte einen Administrator.", "ban-expiry-missing": "Bitte gebe ein Enddatum für diesen Ban an", @@ -104,7 +105,7 @@ "chat-disabled": "Das Chatsystem deaktiviert", "too-many-messages": "Du hast zu viele Nachrichten versandt, bitte warte eine Weile.", "invalid-chat-message": "Ungültige Nachricht", - "chat-message-too-long": "Die Nachricht ist zu lang", + "chat-message-too-long": "Chat Nachricht darf nicht länger als %1 Zeichen sein.", "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": "Du darfst diese Nachricht nicht löschen", diff --git a/public/language/de/flags.json b/public/language/de/flags.json new file mode 100644 index 0000000000..974ba725ff --- /dev/null +++ b/public/language/de/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Zustand", + "reporter": "Meldender", + "reported-at": "Gemeldet am", + "description": "Beschreibung", + "no-flags": "Hurra! Keine Meldungen gefunden.", + "assignee": "Zugeordneter Benutzer", + "update": "Aktualisieren", + "updated": "Aktualisiert", + "target-purged": "Der Inhalt auf den diese Meldung hingewiesen hat, wurde gelöscht und ist nicht mehr verfügbar.", + + "quick-filters": "Schnell-Filter", + "filter-active": "Ein oder mehrere Filter sind in dieser Meldungs-Liste aktiv", + "filter-reset": "Filter Entfernen", + "filters": "Filter Optionen", + "filter-reporterId": "Melder UID", + "filter-targetUid": "Gemeldete UID", + "filter-type": "Meldungstyp", + "filter-type-all": "Gesamter Inhalt", + "filter-type-post": "Beitrag", + "filter-state": "Status", + "filter-assignee": "UID des Zugewiesenen", + "filter-cid": "Kategorie", + "filter-quick-mine": "Mir zugewiesen", + "filter-cid-all": "Alle Kategorien", + "apply-filters": "Filter anwenden", + + "quick-links": "Schnellnavigation", + "flagged-user": "Gemeldeter Benutzer", + "view-profile": "Profil ansehen", + "start-new-chat": "Neuen Chat beginnen", + "go-to-target": "Meldungsziel ansehen", + + "user-view": "Profil ansehen", + "user-edit": "Profil bearbeiten", + + "notes": "Meldungsnotizen", + "add-note": "Notiz hinzufügen", + "no-notes": "Keine geteilten Notizen", + + "history": "Meldungsverlauf", + "back": "Zurück zur Meldungsliste", + "no-history": "Kein Meldungsverlauf", + + "state-all": "Alle Status", + "state-open": "Neu/Öffnen", + "state-wip": "In Arbeit", + "state-resolved": "Gelöst", + "state-rejected": "Abgelehnt", + "no-assignee": "Nicht zugewiesen", + "note-added": "Notiz hinzugefügt", + + "modal-title": "Anstößige Inhalte Melden", + "modal-body": "Bitte geben Sie den Grund an, weshalb Sie %1 %2 melden wollen. Alternativ können Sie einen der Schnell-Meldungs-Knöpfe verwenden, wenn anwendbar.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Beleidigend", + "modal-reason-custom": "Grund für die Meldung dieses Inhalts...", + "modal-submit": "Meldung abschicken", + "modal-submit-success": "Der Inhalt wurde gemeldet." +} \ No newline at end of file diff --git a/public/language/de/login.json b/public/language/de/login.json index acbd636dce..92887d0c2d 100644 --- a/public/language/de/login.json +++ b/public/language/de/login.json @@ -1,5 +1,5 @@ { - "username-email": "Benutzername / E-Mail-Adresse", + "username-email": "Benutzername / Email-Adresse", "username": "Benutzername", "email": "E-Mail", "remember_me": "Eingeloggt bleiben?", diff --git a/public/language/de/modules.json b/public/language/de/modules.json index 47cf83d5c9..bb53b0fb87 100644 --- a/public/language/de/modules.json +++ b/public/language/de/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Monate", "chat.delete_message_confirm": "Bist du sicher, dass du diese Nachricht löschen möchtest?", "chat.add-users-to-room": "Benutzer zum Raum hinzufügen", + "chat.confirm-chat-with-dnd-user": "Dieser Benutzer hat seinen Status auf DnD(Bitte nicht stören) gesetzt. Möchtest du noch immer mit ihm chatten?", "composer.compose": "Verfassen", "composer.show_preview": "Vorschau zeigen", "composer.hide_preview": "Vorschau ausblenden", diff --git a/public/language/de/user.json b/public/language/de/user.json index 3de966f842..22dd586842 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -60,6 +60,7 @@ "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": "Dein Passwort entspricht deiner E-Mail-Adresse, bitte wähle ein anderes Passwort.", + "weak_password": "Schwaches Password.", "upload_picture": "Bild hochladen", "upload_a_picture": "Ein Bild hochladen", "remove_uploaded_picture": "Hochgeladenes Bild entfernen", diff --git a/public/language/el/admin/admin.json b/public/language/el/admin/admin.json index 9c01f56006..cdd706e61c 100644 --- a/public/language/el/admin/admin.json +++ b/public/language/el/admin/admin.json @@ -1,7 +1,7 @@ { - "alert.confirm-reload": "Are you sure you wish to reload NodeBB?", - "alert.confirm-restart": "Are you sure you wish to restart NodeBB?", + "alert.confirm-reload": "Είσαι σίγουρος/η πως θέλεις να επαναφορτώσεις το NodeBB?", + "alert.confirm-restart": "Είσαι σίγουρος/η πως θέλεις να επανεκκινήσεις το NodeBB?", - "acp-title": "%1 | NodeBB Admin Control Panel", - "settings-header-contents": "Contents" + "acp-title": "%1 | NodeBB Πίνακας ελέγχου", + "settings-header-contents": "Περιεχόμενα" } \ No newline at end of file diff --git a/public/language/el/admin/advanced/database.json b/public/language/el/admin/advanced/database.json index f7db6220ee..d4ba886f65 100644 --- a/public/language/el/admin/advanced/database.json +++ b/public/language/el/admin/advanced/database.json @@ -1,11 +1,12 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", "mongo": "Mongo", - "mongo.version": "MongoDB Version", + "mongo.version": "Έκδοση MongoDB", "mongo.storage-engine": "Storage Engine", "mongo.collections": "Collections", "mongo.objects": "Objects", @@ -20,7 +21,7 @@ "mongo.raw-info": "MongoDB Raw Info", "redis": "Redis", - "redis.version": "Redis Version", + "redis.version": "Έκδοση Redis", "redis.connected-clients": "Connected Clients", "redis.connected-slaves": "Connected Slaves", "redis.blocked-clients": "Blocked Clients", diff --git a/public/language/el/admin/manage/flags.json b/public/language/el/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/el/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/el/admin/manage/groups.json b/public/language/el/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/el/admin/manage/groups.json +++ b/public/language/el/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/el/admin/settings/advanced.json b/public/language/el/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/el/admin/settings/advanced.json +++ b/public/language/el/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/el/admin/settings/notifications.json b/public/language/el/admin/settings/notifications.json index 4eff7f341a..10009c6e08 100644 --- a/public/language/el/admin/settings/notifications.json +++ b/public/language/el/admin/settings/notifications.json @@ -1,5 +1,5 @@ { - "notifications": "Notifications", - "welcome-notification": "Welcome Notification", + "notifications": "Ειδοποιήσεις", + "welcome-notification": "Ειδοποίηση καλωσορίσματος", "welcome-notification-link": "Welcome Notification Link" } \ No newline at end of file diff --git a/public/language/el/admin/settings/post.json b/public/language/el/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/el/admin/settings/post.json +++ b/public/language/el/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/el/admin/settings/user.json b/public/language/el/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/el/admin/settings/user.json +++ b/public/language/el/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/el/category.json b/public/language/el/category.json index 33820bce02..d194f5f37e 100644 --- a/public/language/el/category.json +++ b/public/language/el/category.json @@ -1,17 +1,17 @@ { - "category": "Category", - "subcategories": "Subcategories", + "category": "Κατηγορία", + "subcategories": "Υποκατηγορίες", "new_topic_button": "Νέο Θέμα", - "guest-login-post": "Log in to post", + "guest-login-post": "Συνδέσου για να δημοσιεύσεις", "no_topics": "Δεν υπάρχουν θέματα σε αυτή την κατηγορία.
Γιατί δεν δοκιμάζεις να δημοσιεύσεις ένα εσύ;", "browsing": "περιηγούνται", "no_replies": "Κανείς δεν έχει απαντήσει", - "no_new_posts": "No new posts.", + "no_new_posts": "Δεν υπάρχουν νέες δημοσιεύσεις", "share_this_category": "Μοιράσου αυτή την κατηγορία", "watch": "Watch", "ignore": "Αγνόηση", "watching": "Watching", - "ignoring": "Ignoring", + "ignoring": "Αγνόησε", "watching.description": "Show topics in unread", "ignoring.description": "Do not show topics in unread", "watch.message": "You are now watching updates from this category and all subcategories", diff --git a/public/language/el/email.json b/public/language/el/email.json index 3f5adb3021..8cdc4b057d 100644 --- a/public/language/el/email.json +++ b/public/language/el/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "Αυτό είναι ένα δοκιμαστικό email για να επιβεβαιώσουμε ότι ο emailer έχει στηθεί σωστά για το NodeBB.", "unsub.cta": "Κάνε κλικ εδώ για να αλλάξεις αυτές τις ρυθμίσεις", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Ευχαριστούμε!" } \ No newline at end of file diff --git a/public/language/el/error.json b/public/language/el/error.json index 170110489c..cb553a38d1 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "Ο Χρήστης είναι αποκλεισμένος/η", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/el/flags.json b/public/language/el/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/el/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/el/global.json b/public/language/el/global.json index 79965ed553..3a98632abf 100644 --- a/public/language/el/global.json +++ b/public/language/el/global.json @@ -3,11 +3,11 @@ "search": "Αναζήτηση", "buttons.close": "Κλείσιμο", "403.title": "Δεν επιτρέπεται η πρόσβαση", - "403.message": "You seem to have stumbled upon a page that you do not have access to.", + "403.message": "Φαίνεται πως βρέθηκες σε κάποια σελίδα στην οποία δεν έχεις πρόσβαση.", "403.login": "Perhaps you should try logging in?", "404.title": "Δεν βρέθηκε", "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.", - "500.title": "Internal Error.", + "500.title": "Εσωτερικό Σφάλμα.", "500.message": "Ουπς! Φαίνεται πως κάτι πήγε στραβά!", "400.title": "Bad Request.", "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.", @@ -19,19 +19,19 @@ "welcome_back": "Καλωσόρισες Πάλι", "you_have_successfully_logged_in": "Συνδέθηκες με επιτυχία", "save_changes": "Αποθήκευση Αλλαγών", - "save": "Save", + "save": "Αποθήκευση", "close": "Κλείσιμο", "pagination": "Σελιδοποίηση", "pagination.out_of": "%1 από %2", "pagination.enter_index": "Εισαγωγή Σελίδας", "header.admin": "Διαχειριστής", - "header.categories": "Categories", + "header.categories": "Κατηγορίες", "header.recent": "Πρόσφατα", "header.unread": "Μη αναγνωσμένα", "header.tags": "Ετικέτες", "header.popular": "Δημοφιλή", "header.users": "Χρήστες", - "header.groups": "Groups", + "header.groups": "Ομάδες", "header.chats": "Συνομιλίες", "header.notifications": "Ειδοποιήσεις", "header.search": "Αναζήτηση", @@ -60,7 +60,7 @@ "views": "Εμφανίσεις", "reputation": "Φήμη", "read_more": "διάβασε περισσότερα", - "more": "More", + "more": "Περισσότερα", "posted_ago_by_guest": "δημοσιεύτηκε πριν από %1 από Επισκέπτη", "posted_ago_by": "δημοσιεύτηκε πριν από %1 από τον/την %2", "posted_ago": "δημοσιεύτηκε πρίν από %1", @@ -77,7 +77,7 @@ "recentips": "Πρόσφατη IP Σύνδεσης", "moderator_tools": "Moderator Tools", "away": "Απών/ούσα", - "dnd": "Do not disturb", + "dnd": "Μην ενοχλείτε", "invisible": "Αόρατος/η", "offline": "Εκτός Σύνδεσης", "email": "Email", @@ -89,19 +89,19 @@ "privacy": "Privacy", "follow": "Follow", "unfollow": "Unfollow", - "delete_all": "Delete All", + "delete_all": "Διαγραφή Όλων", "map": "Map", "sessions": "Login Sessions", - "ip_address": "IP Address", + "ip_address": "Διεύθυνση IP", "enter_page_number": "Enter page number", - "upload_file": "Upload file", - "upload": "Upload", + "upload_file": "Ανέβασμα αρχείου", + "upload": "Ανέβασμα", "allowed-file-types": "Allowed file types are %1", "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?", "reconnecting-message": "Looks like your connection to %1 was lost, please wait while we try to reconnect.", "play": "Play", "cookies.message": "This website uses cookies to ensure you get the best experience on our website.", "cookies.accept": "Got it!", - "cookies.learn_more": "Learn More", + "cookies.learn_more": "Μάθε Περισσότερα", "edited": "Edited" } \ No newline at end of file diff --git a/public/language/el/groups.json b/public/language/el/groups.json index b5b19b39e9..7c19a8733e 100644 --- a/public/language/el/groups.json +++ b/public/language/el/groups.json @@ -1,13 +1,13 @@ { - "groups": "Groups", + "groups": "Ομάδες", "view_group": "Προβολή Ομάδας", - "owner": "Group Owner", - "new_group": "Create New Group", + "owner": "Κάτοχος Ομάδας", + "new_group": "Δημιουργία Νέας Ομάδας", "no_groups_found": "There are no groups to see", - "pending.accept": "Accept", - "pending.reject": "Reject", - "pending.accept_all": "Accept All", - "pending.reject_all": "Reject All", + "pending.accept": "Αποδοχή", + "pending.reject": "Απόρριψη", + "pending.accept_all": "Αποδοχή Όλων", + "pending.reject_all": "Απόρριψη Όλων", "pending.none": "There are no pending members at this time", "invited.none": "There are no invited members at this time", "invited.uninvite": "Rescind Invitation", @@ -15,7 +15,7 @@ "invited.notification_title": "You have been invited to join %1", "request.notification_title": "Group Membership Request from %1", "request.notification_text": "%1 has requested to become a member of %2", - "cover-save": "Save", + "cover-save": "Αποθήκευση", "cover-saving": "Saving", "details.title": "Λεπτομέρειες Ομάδας", "details.members": "Λίστα Μελών", @@ -31,8 +31,8 @@ "details.owner_options": "Group Administration", "details.group_name": "Group Name", "details.member_count": "Member Count", - "details.creation_date": "Creation Date", - "details.description": "Description", + "details.creation_date": "Ημερομηνία Δημιουργίας", + "details.description": "Περιγραφή", "details.badge_preview": "Badge Preview", "details.change_icon": "Change Icon", "details.change_colour": "Change Colour", @@ -41,7 +41,7 @@ "details.private_help": "If enabled, joining of groups requires approval from a group owner", "details.hidden": "Hidden", "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually", - "details.delete_group": "Delete Group", + "details.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/el/login.json b/public/language/el/login.json index 126c06b283..11cc226b06 100644 --- a/public/language/el/login.json +++ b/public/language/el/login.json @@ -1,6 +1,6 @@ { - "username-email": "Username / Email", - "username": "Username", + "username-email": "Όνομα χρήστη / Email", + "username": "Όνομα Χρήστη", "email": "Email", "remember_me": "Απομνημόνευση;", "forgot_password": "Ξέχασες τον κωδικό σου;", diff --git a/public/language/el/modules.json b/public/language/el/modules.json index e2d45cd043..275b03ce5a 100644 --- a/public/language/el/modules.json +++ b/public/language/el/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Months", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/el/pages.json b/public/language/el/pages.json index 243f8511da..242d4309e6 100644 --- a/public/language/el/pages.json +++ b/public/language/el/pages.json @@ -8,15 +8,15 @@ "recent": "Πρόσφατα Θέματα", "flagged-content": "Flagged Content", "ip-blacklist": "IP Blacklist", - "users/online": "Online Users", - "users/latest": "Latest Users", + "users/online": "Συνδεδεμένοι Χρήστες", + "users/latest": "Πρόσφατοι Χρήστες", "users/sort-posts": "Users with the most posts", "users/sort-reputation": "Users with the most reputation", - "users/banned": "Banned Users", + "users/banned": "Αποκλεισμένοι Χρήστες", "users/most-flags": "Most flagged users", - "users/search": "User Search", + "users/search": "Αναζήτηση Χρήστη", "notifications": "Ειδοποιήσεις", - "tags": "Tags", + "tags": "Ετικέτες", "tag": "Topics tagged under \"%1\"", "register": "Register an account", "registration-complete": "Registration complete", @@ -33,14 +33,14 @@ "account/edit/password": "Editing password of \"%1\"", "account/edit/username": "Editing username of \"%1\"", "account/edit/email": "Editing email of \"%1\"", - "account/info": "Account Info", + "account/info": "Πληροφορίες Λογαρισμού", "account/following": "People %1 follows", "account/followers": "People who follow %1", "account/posts": "Posts made by %1", "account/topics": "Topics created by %1", "account/groups": "%1's Groups", "account/bookmarks": "%1's Bookmarked Posts", - "account/settings": "User Settings", + "account/settings": "Επιλογές Χρήστη", "account/watched": "Topics watched by %1", "account/upvoted": "Posts upvoted by %1", "account/downvoted": "Posts downvoted by %1", diff --git a/public/language/el/recent.json b/public/language/el/recent.json index 8424eb4616..85f948e9c8 100644 --- a/public/language/el/recent.json +++ b/public/language/el/recent.json @@ -6,8 +6,8 @@ "year": "Έτος", "alltime": "Όλο το Ιστορικό", "no_recent_topics": "Δεν υπάρχουν πρόσφατα θέματα.", - "no_popular_topics": "There are no popular topics.", - "there-is-a-new-topic": "There is a new topic.", + "no_popular_topics": "Δεν υπάρχουν δημοφιλή θέματα.", + "there-is-a-new-topic": "Υπάρχει ένα νέο θέμα.", "there-is-a-new-topic-and-a-new-post": "There is a new topic and a new post.", "there-is-a-new-topic-and-new-posts": "There is a new topic and %1 new posts.", "there-are-new-topics": "There are %1 new topics.", diff --git a/public/language/el/reset_password.json b/public/language/el/reset_password.json index 4df4164ac3..beee413045 100644 --- a/public/language/el/reset_password.json +++ b/public/language/el/reset_password.json @@ -9,9 +9,9 @@ "repeat_password": "Επιβεβαίωση Κωδικού", "enter_email": "Παρακαλώ γράψε την διεύθυνση email σου και θα σου στείλουμε ένα email με οδηγίες για το πως να επαναφέρεις τον λογαριασμό σου.", "enter_email_address": "Εισαγωγή Διεύθυνσης Email", - "password_reset_sent": "Password Reset Sent", - "invalid_email": "Invalid Email / Email does not exist!", - "password_too_short": "The password entered is too short, please pick a different password.", - "passwords_do_not_match": "The two passwords you've entered do not match.", - "password_expired": "Your password has expired, please choose a new password" + "password_reset_sent": "Η επαναφορά κωδικού στάλθηκε", + "invalid_email": "Λάθος Email ή το Email δεν υπάρχει!", + "password_too_short": "Ο κωδικός είναι πολύ μικρός, παρακαλώ επέλεξε διαφορετικό.", + "passwords_do_not_match": "Οι κωδικοί δεν ταιριάζουν μεταξύ τους.", + "password_expired": "Ο κωδικός έληξε, παρακαλώ επίλεξε νέο κωδικό" } \ No newline at end of file diff --git a/public/language/el/search.json b/public/language/el/search.json index 61aa1e3128..fb3094590e 100644 --- a/public/language/el/search.json +++ b/public/language/el/search.json @@ -9,21 +9,21 @@ "in-categories": "In Categories", "search-child-categories": "Search child categories", "has-tags": "Has tags", - "reply-count": "Reply Count", - "at-least": "At least", + "reply-count": "Αριθμός Απαντήσεων", + "at-least": "Τουλάχιστον", "at-most": "At most", "relevance": "Relevance", "post-time": "Post time", - "newer-than": "Newer than", - "older-than": "Older than", + "newer-than": "Νεότερο από", + "older-than": "Παλαιότερο από", "any-date": "Any date", - "yesterday": "Yesterday", - "one-week": "One week", - "two-weeks": "Two weeks", - "one-month": "One month", - "three-months": "Three months", + "yesterday": "Χθες", + "one-week": "Μία εβδομάδα", + "two-weeks": "Δύο εβδομάδες", + "one-month": "Ένας μήνας", + "three-months": "Τρεις μήνες", "six-months": "Six months", - "one-year": "One year", + "one-year": "Ένας χρόνος", "sort-by": "Sort by", "last-reply-time": "Last reply time", "topic-title": "Topic title", @@ -31,7 +31,7 @@ "number-of-views": "Number of views", "topic-start-date": "Topic start date", "username": "Username", - "category": "Category", + "category": "Κατηγορία", "descending": "In descending order", "ascending": "In ascending order", "save-preferences": "Save preferences", diff --git a/public/language/el/unread.json b/public/language/el/unread.json index 803596873f..9d5af4e5a2 100644 --- a/public/language/el/unread.json +++ b/public/language/el/unread.json @@ -5,9 +5,9 @@ "mark_as_read": "Σημείωση ώς Αναγνωσμένα", "selected": "Επιλεγμένα", "all": "Όλα", - "all_categories": "All categories", + "all_categories": "Όλες οι κατηγορίες", "topics_marked_as_read.success": "Τα θέματα σημειώθηκαν ως αναγνωσμένα!", - "all-topics": "All Topics", - "new-topics": "New Topics", + "all-topics": "Όλα τα θέματα", + "new-topics": "Νέα Θέματα", "watched-topics": "Watched Topics" } \ No newline at end of file diff --git a/public/language/el/uploads.json b/public/language/el/uploads.json index 1622cb5693..08a9da99dd 100644 --- a/public/language/el/uploads.json +++ b/public/language/el/uploads.json @@ -1,6 +1,6 @@ { - "uploading-file": "Uploading the file...", - "select-file-to-upload": "Select a file to upload!", - "upload-success": "File uploaded successfully!", - "maximum-file-size": "Maximum %1 kb" + "uploading-file": "Το αρχείο ανεβαίνει...", + "select-file-to-upload": "Επίλεξε αρχείο για ανέβασμα!", + "upload-success": "Το αρχείο ανέβηκε επιτυχώς!", + "maximum-file-size": "Μέγιστο %1 kb" } \ No newline at end of file diff --git a/public/language/el/user.json b/public/language/el/user.json index b2e54c9479..dd17f88ac4 100644 --- a/public/language/el/user.json +++ b/public/language/el/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Το όνομα χρήστη που ζήτησες χρησιμοποιείται ήδη, οπότε το τροποποιήσαμε λίγο. Πλέον είσαι γνωστός/ή ώς %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Ανέβασμα φωτογραφίας", "upload_a_picture": "Ανέβασε μια φωτογραφία", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/el/users.json b/public/language/el/users.json index be5f770f99..1e97404f00 100644 --- a/public/language/el/users.json +++ b/public/language/el/users.json @@ -8,14 +8,14 @@ "load_more": "Φόρτωση περισσότερων", "users-found-search-took": "%1 user(s) found! Search took %2 seconds.", "filter-by": "Filter By", - "online-only": "Online only", - "invite": "Invite", + "online-only": "Μόνο Συνδεδεμένοι", + "invite": "Πρόσκληση", "invitation-email-sent": "An invitation email has been sent to %1", - "user_list": "User List", - "recent_topics": "Recent Topics", - "popular_topics": "Popular Topics", - "unread_topics": "Unread Topics", - "categories": "Categories", - "tags": "Tags", - "no-users-found": "No users found!" + "user_list": "Λίστα Χρηστών", + "recent_topics": "Πρόσφατα Θέματα", + "popular_topics": "Δημοφιλή Θέματα", + "unread_topics": "Μη αναγνωσμένα Θέματα", + "categories": "Κατηγορίες", + "tags": "Ετικέτες", + "no-users-found": "Δε βρέθηκαν χρήστες!" } \ No newline at end of file diff --git a/public/language/en-GB/admin/advanced/database.json b/public/language/en-GB/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/en-GB/admin/advanced/database.json +++ b/public/language/en-GB/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/en-GB/admin/development/info.json b/public/language/en-GB/admin/development/info.json index b2768ca212..24bf179655 100644 --- a/public/language/en-GB/admin/development/info.json +++ b/public/language/en-GB/admin/development/info.json @@ -1,5 +1,6 @@ { "you-are-on": "Info - You are on %1:%2", + "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", "nodejs": "nodejs", diff --git a/public/language/en-GB/admin/general/languages.json b/public/language/en-GB/admin/general/languages.json index da45cade2c..bdd57849b3 100644 --- a/public/language/en-GB/admin/general/languages.json +++ b/public/language/en-GB/admin/general/languages.json @@ -1,5 +1,6 @@ { "language-settings": "Language Settings", "description": "The default language determines the language settings for all users who are visiting your forum.
Individual users can override the default language on their account settings page.", - "default-language": "Default Language" + "default-language": "Default Language", + "auto-detect": "Auto Detect Language Setting for Guests" } \ No newline at end of file diff --git a/public/language/en-GB/admin/manage/flags.json b/public/language/en-GB/admin/manage/flags.json deleted file mode 100644 index 8286861d01..0000000000 --- a/public/language/en-GB/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/en-GB/admin/manage/groups.json b/public/language/en-GB/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/en-GB/admin/manage/groups.json +++ b/public/language/en-GB/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/en-GB/admin/menu.json b/public/language/en-GB/admin/menu.json index 6a4995ea6e..985c540e8a 100644 --- a/public/language/en-GB/admin/menu.json +++ b/public/language/en-GB/admin/menu.json @@ -13,7 +13,6 @@ "manage/users": "Users", "manage/registration": "Registration Queue", "manage/groups": "Groups", - "manage/flags": "Flags", "manage/ip-blacklist": "IP Blacklist", "section-settings": "Settings", diff --git a/public/language/en-GB/admin/settings/general.json b/public/language/en-GB/admin/settings/general.json index 8db88bb958..3f2814bd88 100644 --- a/public/language/en-GB/admin/settings/general.json +++ b/public/language/en-GB/admin/settings/general.json @@ -27,5 +27,6 @@ "touch-icon.help": "Recommended size and format: 192x192, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.", "outgoing-links": "Outgoing Links", "outgoing-links.warning-page": "Use Outgoing Links Warning Page", + "search-default-sort-by": "Search default sort by", "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page" } \ No newline at end of file diff --git a/public/language/en-GB/admin/settings/post.json b/public/language/en-GB/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/en-GB/admin/settings/post.json +++ b/public/language/en-GB/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/en-GB/email.json b/public/language/en-GB/email.json index e893709772..a55f4f1f52 100644 --- a/public/language/en-GB/email.json +++ b/public/language/en-GB/email.json @@ -44,5 +44,10 @@ "unsub.cta": "Click here to alter those settings", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", + "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index b5b90c04e5..89222f622f 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -15,6 +15,7 @@ "invalid-title": "Invalid title", "invalid-user-data": "Invalid User Data", "invalid-password": "Invalid Password", + "invalid-login-credentials": "Invalid login credentials", "invalid-username-or-password": "Please specify both a username and password", "invalid-search-term": "Invalid search term", "csrf-invalid": "We were unable to log you in, likely due to an expired session. Please try again", @@ -37,6 +38,7 @@ "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -129,7 +131,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/en-GB/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/en-GB/modules.json b/public/language/en-GB/modules.json index 5f258128f8..8f8e4ad1ed 100644 --- a/public/language/en-GB/modules.json +++ b/public/language/en-GB/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Months", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", diff --git a/public/language/en-GB/notifications.json b/public/language/en-GB/notifications.json index 5a2ed58908..8c7ed9d07a 100644 --- a/public/language/en-GB/notifications.json +++ b/public/language/en-GB/notifications.json @@ -12,6 +12,17 @@ "new_notification": "New Notification", "you_have_unread_notifications": "You have unread notifications.", + "all": "All", + "topics": "Topics", + "replies": "Replies", + "chat": "Chats", + "follows": "Follows", + "upvote": "Upvotes", + "new-flags": "New Flags", + "my-flags": "Flags assigned to me", + "bans": "Bans", + + "new_message_from": "New message from %1", "upvoted_your_post_in": "%1 has upvoted your post in %2.", "upvoted_your_post_in_dual": "%1 and %2 have upvoted your post in %3.", @@ -21,6 +32,9 @@ "user_flagged_post_in": "%1 flagged a post in %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_user": "%1 flagged a user profile (%2)", + "user_flagged_user_dual": "%1 and %2 flagged a user profile (%3)", + "user_flagged_user_multiple": "%1 and %2 others flagged a user profile (%3)", "user_posted_to" : "%1 has posted a reply to: %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", @@ -30,6 +44,7 @@ "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.", + "flag_assigned_to_you": "Flag %1 has been assigned to you", "email-confirmed": "Email Confirmed", "email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.", diff --git a/public/language/en-GB/pages.json b/public/language/en-GB/pages.json index 801b28edea..5efa686fc3 100644 --- a/public/language/en-GB/pages.json +++ b/public/language/en-GB/pages.json @@ -6,7 +6,7 @@ "popular-month": "Popular topics this month", "popular-alltime": "All time popular topics", "recent": "Recent Topics", - "flagged-posts": "Flagged Posts", + "flagged-content": "Flagged Content", "ip-blacklist": "IP Blacklist", "users/online": "Online Users", @@ -32,6 +32,9 @@ "chats": "Chats", "chat": "Chatting with %1", + "flags": "Flags", + "flag-details": "Flag %1 Details", + "account/edit": "Editing \"%1\"", "account/edit/password": "Editing password of \"%1\"", "account/edit/username": "Editing username of \"%1\"", diff --git a/public/language/en-GB/search.json b/public/language/en-GB/search.json index 98c1afcea2..51a0a76ebb 100644 --- a/public/language/en-GB/search.json +++ b/public/language/en-GB/search.json @@ -12,6 +12,7 @@ "reply-count": "Reply Count", "at-least": "At least", "at-most": "At most", + "relevance": "Relevance", "post-time": "Post time", "newer-than": "Newer than", "older-than": "Older than", diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index 29a85c15cc..b960fb0fba 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -16,7 +16,8 @@ "notify_me": "Be notified of new replies in this topic", "quote": "Quote", "reply": "Reply", - "replies_to_this_post": "Replies: %1", + "replies_to_this_post": "%1 Replies", + "last_reply_time": "Last reply", "reply-as-topic": "Reply as topic", "guest-login-reply": "Log in to reply", "edit": "Edit", @@ -28,7 +29,6 @@ "link": "Link", "share": "Share", "tools": "Tools", - "flag": "Flag", "locked": "Locked", "pinned": "Pinned", "moved": "Moved", @@ -36,22 +36,6 @@ "bookmark_instructions" : "Click here to return to the last read post in this thread.", "flag_title": "Flag this post for moderation", - "flag_success": "This post has been flagged for moderation.", - "flag_manage_title": "Flagged post in %1", - "flag_manage_history": "Action History", - "flag_manage_no_history": "No event history to report", - "flag_manage_assignee": "Assignee", - "flag_manage_state": "State", - "flag_manage_state_open": "New/Open", - "flag_manage_state_wip": "Work in Progress", - "flag_manage_state_resolved": "Resolved", - "flag_manage_state_rejected": "Rejected", - "flag_manage_notes": "Shared Notes", - "flag_manage_update": "Update Flag Status", - "flag_manage_history_assignee": "Assigned to %1", - "flag_manage_history_state": "Updated state to %1", - "flag_manage_history_notes": "Updated flag notes", - "flag_manage_saved": "Flag Details Updated", "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.", @@ -153,10 +137,5 @@ "stale.create": "Create a new topic", "stale.reply_anyway": "Reply to this topic anyway", - "link_back": "Re: [%1](%2)\n\n", - - "spam": "Spam", - "offensive": "Offensive", - "custom-flag-reason": "Enter a flagging reason" - + "link_back": "Re: [%1](%2)\n\n" } diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index 8a5206d272..2f5e588881 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -35,6 +35,7 @@ "chat": "Chat", "chat_with": "Continue chat with %1", "new_chat_with": "Start new chat with %1", + "flag-profile": "Flag Profile", "follow": "Follow", "unfollow": "Unfollow", "more": "More", @@ -149,5 +150,6 @@ "info.username-history": "Username History", "info.email-history": "Email History", "info.moderation-note": "Moderation Note", - "info.moderation-note.success": "Moderation note saved" + "info.moderation-note.success": "Moderation note saved", + "info.moderation-note.add": "Add note" } diff --git a/public/language/en-US/admin/advanced/database.json b/public/language/en-US/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/en-US/admin/advanced/database.json +++ b/public/language/en-US/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/en-US/admin/manage/flags.json b/public/language/en-US/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/en-US/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/en-US/admin/manage/groups.json b/public/language/en-US/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/en-US/admin/manage/groups.json +++ b/public/language/en-US/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/en-US/admin/settings/advanced.json b/public/language/en-US/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/en-US/admin/settings/advanced.json +++ b/public/language/en-US/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/en-US/admin/settings/post.json b/public/language/en-US/admin/settings/post.json index 832372a941..7ee040cf20 100644 --- a/public/language/en-US/admin/settings/post.json +++ b/public/language/en-US/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/en-US/admin/settings/user.json b/public/language/en-US/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/en-US/admin/settings/user.json +++ b/public/language/en-US/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/en-US/email.json b/public/language/en-US/email.json index 691e6309a2..c1e17018fa 100644 --- a/public/language/en-US/email.json +++ b/public/language/en-US/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", "unsub.cta": "Click here to alter those settings", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json index 3149dadc15..35eaf8cbc6 100644 --- a/public/language/en-US/error.json +++ b/public/language/en-US/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/en-US/flags.json b/public/language/en-US/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/en-US/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/en-US/modules.json b/public/language/en-US/modules.json index e2d45cd043..275b03ce5a 100644 --- a/public/language/en-US/modules.json +++ b/public/language/en-US/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Months", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/en-US/user.json b/public/language/en-US/user.json index 6b2c7e133c..19f71e5e6a 100644 --- a/public/language/en-US/user.json +++ b/public/language/en-US/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Upload picture", "upload_a_picture": "Upload a picture", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/en-x-pirate/admin/advanced/database.json b/public/language/en-x-pirate/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/en-x-pirate/admin/advanced/database.json +++ b/public/language/en-x-pirate/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/en-x-pirate/admin/manage/flags.json b/public/language/en-x-pirate/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/en-x-pirate/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/manage/groups.json b/public/language/en-x-pirate/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/en-x-pirate/admin/manage/groups.json +++ b/public/language/en-x-pirate/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/en-x-pirate/admin/settings/advanced.json b/public/language/en-x-pirate/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/en-x-pirate/admin/settings/advanced.json +++ b/public/language/en-x-pirate/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/en-x-pirate/admin/settings/post.json b/public/language/en-x-pirate/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/en-x-pirate/admin/settings/post.json +++ b/public/language/en-x-pirate/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/en-x-pirate/admin/settings/user.json b/public/language/en-x-pirate/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/en-x-pirate/admin/settings/user.json +++ b/public/language/en-x-pirate/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/en-x-pirate/email.json b/public/language/en-x-pirate/email.json index 1ffc2ef7a7..d46ef9d972 100644 --- a/public/language/en-x-pirate/email.json +++ b/public/language/en-x-pirate/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", "unsub.cta": "Click here to alter those settings", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json index 3149dadc15..35eaf8cbc6 100644 --- a/public/language/en-x-pirate/error.json +++ b/public/language/en-x-pirate/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/en-x-pirate/flags.json b/public/language/en-x-pirate/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/en-x-pirate/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/en-x-pirate/modules.json b/public/language/en-x-pirate/modules.json index d2c33e827f..8d12a6f964 100644 --- a/public/language/en-x-pirate/modules.json +++ b/public/language/en-x-pirate/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Months", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/en-x-pirate/user.json b/public/language/en-x-pirate/user.json index a789b5d683..b28bcd5ffe 100644 --- a/public/language/en-x-pirate/user.json +++ b/public/language/en-x-pirate/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Upload picture", "upload_a_picture": "Upload a picture", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/es/admin/advanced/database.json b/public/language/es/admin/advanced/database.json index f7db6220ee..9c51814073 100644 --- a/public/language/es/admin/advanced/database.json +++ b/public/language/es/admin/advanced/database.json @@ -1,34 +1,35 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", "mongo": "Mongo", "mongo.version": "MongoDB Version", "mongo.storage-engine": "Storage Engine", - "mongo.collections": "Collections", - "mongo.objects": "Objects", - "mongo.avg-object-size": "Avg. Object Size", - "mongo.data-size": "Data Size", + "mongo.collections": "Colecciones", + "mongo.objects": "Objetos", + "mongo.avg-object-size": "Tamaño promedio por Objeto", + "mongo.data-size": "Tamaño de los Datos", "mongo.storage-size": "Storage Size", "mongo.index-size": "Index Size", "mongo.file-size": "File Size", "mongo.resident-memory": "Resident Memory", - "mongo.virtual-memory": "Virtual Memory", + "mongo.virtual-memory": "Memoria Virtual", "mongo.mapped-memory": "Mapped Memory", "mongo.raw-info": "MongoDB Raw Info", "redis": "Redis", "redis.version": "Redis Version", "redis.connected-clients": "Connected Clients", - "redis.connected-slaves": "Connected Slaves", - "redis.blocked-clients": "Blocked Clients", - "redis.used-memory": "Used Memory", + "redis.connected-slaves": "Esclavos Conectados", + "redis.blocked-clients": "Clientes Bloqueados", + "redis.used-memory": "Memoria Utilizada", "redis.memory-frag-ratio": "Memory Fragmentation Ratio", - "redis.total-connections-recieved": "Total Connections Received", - "redis.total-commands-processed": "Total Commands Processed", - "redis.iops": "Instantaneous Ops. Per Second", + "redis.total-connections-recieved": "Total de Conexiones Recividas ", + "redis.total-commands-processed": "Total de Comandos Procesados", + "redis.iops": "Operaciones Instantáneas por Segundo", "redis.keyspace-hits": "Keyspace Hits", "redis.keyspace-misses": "Keyspace Misses", "redis.raw-info": "Redis Raw Info" diff --git a/public/language/es/admin/appearance/skins.json b/public/language/es/admin/appearance/skins.json index 4db6fbdd8a..3f1a331fc2 100644 --- a/public/language/es/admin/appearance/skins.json +++ b/public/language/es/admin/appearance/skins.json @@ -1,9 +1,9 @@ { - "loading": "Loading Skins...", - "homepage": "Homepage", - "select-skin": "Select Skin", - "current-skin": "Current Skin", - "skin-updated": "Skin Updated", - "applied-success": "%1 skin was succesfully applied", - "revert-success": "Skin reverted to base colours" + "loading": "Cargando Temas...", + "homepage": "Pagina Principal", + "select-skin": "Selecciona el Tema", + "current-skin": "Tema Actual", + "skin-updated": "Tema Actualizado", + "applied-success": "El tema %1 se aplicó correctamente", + "revert-success": "El tema revierte los colores base" } \ No newline at end of file diff --git a/public/language/es/admin/appearance/themes.json b/public/language/es/admin/appearance/themes.json index 3148a01337..ab93165d08 100644 --- a/public/language/es/admin/appearance/themes.json +++ b/public/language/es/admin/appearance/themes.json @@ -1,11 +1,11 @@ { - "checking-for-installed": "Checking for installed themes...", - "homepage": "Homepage", - "select-theme": "Select Theme", - "current-theme": "Current Theme", - "no-themes": "No installed themes found", - "revert-confirm": "Are you sure you wish to restore the default NodeBB theme?", - "theme-changed": "Theme Changed", - "revert-success": "You have successfully reverted your NodeBB back to it's default theme.", - "restart-to-activate": "Please restart your NodeBB to fully activate this theme" + "checking-for-installed": "Buscando los temas instalados...", + "homepage": "Pagina Principal", + "select-theme": "Tema Seleccionado", + "current-theme": "Tema Actual ", + "no-themes": "No se encontraron temas instalados", + "revert-confirm": "¿Estas seguro/a que quieres restaurar el tema de fabrica de NodeBB?", + "theme-changed": "Se Cambió el Tema", + "revert-success": "Has revertido con exito el tema de fabrica de NodeBB.", + "restart-to-activate": "Por favor reinicia NodeBB para activar por completo este tema." } \ No newline at end of file diff --git a/public/language/es/admin/development/info.json b/public/language/es/admin/development/info.json index b2768ca212..a068347c43 100644 --- a/public/language/es/admin/development/info.json +++ b/public/language/es/admin/development/info.json @@ -1,11 +1,11 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "Info - Tu estas en %1:%2", "host": "host", "pid": "pid", "nodejs": "nodejs", - "online": "online", + "online": "en-linea", "git": "git", - "load": "load", + "load": "cargar", "uptime": "uptime", "registered": "Registered", diff --git a/public/language/es/admin/extend/plugins.json b/public/language/es/admin/extend/plugins.json index 4bb8cff1c1..dba3e65d87 100644 --- a/public/language/es/admin/extend/plugins.json +++ b/public/language/es/admin/extend/plugins.json @@ -1,41 +1,41 @@ { "installed": "Instalado", - "active": "Active", - "inactive": "Inactive", - "out-of-date": "Out of Date", - "none-found": "No plugins found.", - "none-active": "No Active Plugins", - "find-plugins": "Find Plugins", + "active": "Activo", + "inactive": "Inactivo ", + "out-of-date": "Desactualizado", + "none-found": "No se encontraron plugins.", + "none-active": "No hay Plug-ins activos", + "find-plugins": "Buscar Plug-in", - "plugin-search": "Plugin Search", - "plugin-search-placeholder": "Search for plugin...", - "reorder-plugins": "Re-order Plugins", - "order-active": "Order Active Plugins", - "dev-interested": "Interested in writing plugins for NodeBB?", + "plugin-search": "Plug-in de Búsqueda", + "plugin-search-placeholder": "Búscando Plug-in", + "reorder-plugins": "Re-ordenar Plug-ins", + "order-active": "Ordenar Plug-ins Activos", + "dev-interested": "¿Estas interesado en escribir plug-ins para NodeBB?", "docs-info": "Full documentation regarding plugin authoring can be found in the NodeBB Docs Portal.", - "order.description": "Certain plugins work ideally when they are initialised before/after other plugins.", - "order.explanation": "Plugins load in the order specified here, from top to bottom", + "order.description": "Algunos plug-in funcionan idealmente cuando son inicializados antes o despues de otros.", + "order.explanation": "Los plug-in son cargados en el orden especificado, de arriba a abajo.", - "plugin-item.themes": "Themes", - "plugin-item.deactivate": "Deactivate", - "plugin-item.activate": "Activate", - "plugin-item.install": "Install", - "plugin-item.uninstall": "Uninstall", - "plugin-item.settings": "Settings", - "plugin-item.installed": "Installed", - "plugin-item.latest": "Latest", - "plugin-item.upgrade": "Upgrade", - "plugin-item.more-info": "For more information:", - "plugin-item.unknown": "Unknown", - "plugin-item.unknown-explanation": "The state of this plugin could not be determined, possibly due to a misconfiguration error.", + "plugin-item.themes": "Temas", + "plugin-item.deactivate": "Desactivado", + "plugin-item.activate": "Activado", + "plugin-item.install": "Instalar", + "plugin-item.uninstall": "Desinstalar", + "plugin-item.settings": "Configuraciones", + "plugin-item.installed": "Instalados", + "plugin-item.latest": "Ultimos", + "plugin-item.upgrade": "Actualizado", + "plugin-item.more-info": "Para mas información:", + "plugin-item.unknown": "Desconocido", + "plugin-item.unknown-explanation": "El estado de este plug-in no puede determinsarse, posiblemente es debido a un error de configuración.", - "alert.enabled": "Plugin Enabled", - "alert.disabled": "Plugin Disabled", - "alert.upgraded": "Plugin Upgraded", - "alert.installed": "Plugin Installed", - "alert.uninstalled": "Plugin Uninstalled", - "alert.activate-success": "Please restart your NodeBB to fully activate this plugin", + "alert.enabled": "El plug-in esta Activo", + "alert.disabled": "Plug-in Des-habilitado", + "alert.upgraded": "Plug-in Actualizado", + "alert.installed": "Plug-in Instalado", + "alert.uninstalled": "Plug-in Desinstalado", + "alert.activate-success": "Por favor reinicia NodeBB para activar el plug-in por completo", "alert.deactivate-success": "Plugin successfully deactivated", "alert.upgrade-success": "Please reload your NodeBB to fully upgrade this plugin", "alert.install-success": "Plugin successfully installed, please activate the plugin.", diff --git a/public/language/es/admin/general/dashboard.json b/public/language/es/admin/general/dashboard.json index 02046bd17a..c68d48da21 100644 --- a/public/language/es/admin/general/dashboard.json +++ b/public/language/es/admin/general/dashboard.json @@ -1,64 +1,64 @@ { - "forum-traffic": "Forum Traffic", - "page-views": "Page Views", - "unique-visitors": "Unique Visitors", - "users": "Users", - "posts": "Posts", - "topics": "Topics", - "page-views-last-month": "Page views Last Month", - "page-views-this-month": "Page views This Month", - "page-views-last-day": "Page views in last 24 hours", + "forum-traffic": "Trafico del Foro", + "page-views": "Vistas de la Pagina", + "unique-visitors": "Visitantes Unicos", + "users": "Usuario", + "posts": "Publicación", + "topics": "Temas", + "page-views-last-month": "Vistas de la Pagina del Mes Pasado", + "page-views-this-month": "Vistas de la Pagina de este Mes.", + "page-views-last-day": "Vistas de la Pagina en las ultimas 24 horas", - "stats.day": "Day", - "stats.week": "Week", - "stats.month": "Month", + "stats.day": "Día", + "stats.week": "Semana", + "stats.month": "Mes", "stats.all": "All Time", - "updates": "Updates", - "running-version": "You are running NodeBB v%1.", - "keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.", + "updates": "Actualizaciones", + "running-version": "Estas ejecutando NodeBB v%1.", + "keep-updated": "Asegúrate que tu NodeBB este al día en los últimos parches de seguridad y actualizaciones.", "up-to-date": "

You are up-to-date

", - "upgrade-available": "

A new version (v%1) has been released. Consider upgrading your NodeBB.

", - "prerelease-upgrade-available": "

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

", - "prerelease-warning": "

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

", - "running-in-development": "Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator.", + "upgrade-available": "

La nueva versión (v%1) ha sido lanzada. Considera actualizar tu NodeBB.

", + "prerelease-upgrade-available": "

Esta es una versión antigua pre-lanzamiento de NodeBB. La nueva versión (v%1) ha sido lanzada. Considera actualizar tu NodeBB.

", + "prerelease-warning": "

Esta es una versión depre-lanzamiento de NodeBB. Algunas fallas pueden ocurrir.

", + "running-in-development": "Forum esta siendo ejecutado en modo de desarrollador. El foro puede estar abierto a vulnerabilidades potenciales; por favor contacta tu administrador del sistema.", - "notices": "Notices", - "restart-not-required": "Restart not required", - "restart-required": "Restart required", - "search-plugin-installed": "Search Plugin installed", - "search-plugin-not-installed": "Search Plugin not installed", - "search-plugin-tooltip": "Install a search plugin from the plugin page in order to activate search functionality", + "notices": "Noticias", + "restart-not-required": "No se require reiniciar.", + "restart-required": "Se requiere reiniciar", + "search-plugin-installed": "El plug-in de búsqueda esta instalado.", + "search-plugin-not-installed": "El plug-in de busqueda no esta instalado", + "search-plugin-tooltip": "Instala el plug-in de búsqueda desde la pagina de plugins para activar esta funcionalidad.", - "control-panel": "System Control", - "reload": "Reload", - "restart": "Restart", - "restart-warning": "Reloading or Restarting your NodeBB will drop all existing connections for a few seconds.", - "maintenance-mode": "Maintenance Mode", - "maintenance-mode-title": "Click here to set up maintenance mode for NodeBB", - "realtime-chart-updates": "Realtime Chart Updates", + "control-panel": "Control del Systema", + "reload": "Recargar", + "restart": "Reiniciar", + "restart-warning": "Recargar o Reiniciar tu NodeBB va a tumbar todas las conexiones existentes por algunos segundos.", + "maintenance-mode": "Modo de Mantenimiento", + "maintenance-mode-title": "Haz clic aquí para activar el modo de mantenimiento de NodeBB", + "realtime-chart-updates": "Actualizar el Grafo en Tiempo Real", - "active-users": "Active Users", - "active-users.users": "Users", - "active-users.guests": "Guests", + "active-users": "Usuarios Activos", + "active-users.users": "Usuarios", + "active-users.guests": "Invitados", "active-users.total": "Total", - "active-users.connections": "Connections", + "active-users.connections": "Conexiones", - "anonymous-registered-users": "Anonymous vs Registered Users", - "anonymous": "Anonymous", - "registered": "Registered", + "anonymous-registered-users": "Usuarios Anónimos vs Registrados", + "anonymous": "Anónimos", + "registered": "Registrados", - "user-presence": "User Presence", - "on-categories": "On categories list", + "user-presence": "Presencia del Usuario", + "on-categories": "Listado en Categorias", "reading-posts": "Reading posts", "browsing-topics": "Browsing topics", - "recent": "Recent", - "unread": "Unread", + "recent": "Recientes", + "unread": "Sin Leer", - "high-presence-topics": "High Presence Topics", + "high-presence-topics": "Temas con Alta Presencia", - "graphs.page-views": "Page Views", - "graphs.unique-visitors": "Unique Visitors", - "graphs.registered-users": "Registered Users", - "graphs.anonymous-users": "Anonymous Users" + "graphs.page-views": "Vista de la Pagina", + "graphs.unique-visitors": "Visitantes Unicos", + "graphs.registered-users": "Usuarios Registrados", + "graphs.anonymous-users": "Usuarios Anónimos" } diff --git a/public/language/es/admin/general/homepage.json b/public/language/es/admin/general/homepage.json index 4866b8baf6..4b9ecd81c8 100644 --- a/public/language/es/admin/general/homepage.json +++ b/public/language/es/admin/general/homepage.json @@ -1,7 +1,7 @@ { - "home-page": "Home Page", - "description": "Choose what page is shown when users navigate to the root URL of your forum.", - "home-page-route": "Home Page Route", + "home-page": "Página Principal", + "description": "Escoge que pagina se muestra cuando los usuarios navegan en la raíz del foro.", + "home-page-route": "Ruta de la Pagina Principal", "custom-route": "Custom Route", - "allow-user-home-pages": "Allow User Home Pages" + "allow-user-home-pages": "Permitir Pagina de Perfil del Usuario" } \ No newline at end of file diff --git a/public/language/es/admin/manage/flags.json b/public/language/es/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/es/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/es/admin/manage/groups.json b/public/language/es/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/es/admin/manage/groups.json +++ b/public/language/es/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/es/admin/settings/advanced.json b/public/language/es/admin/settings/advanced.json index 31a15df457..794cdb643d 100644 --- a/public/language/es/admin/settings/advanced.json +++ b/public/language/es/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/es/admin/settings/post.json b/public/language/es/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/es/admin/settings/post.json +++ b/public/language/es/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/es/admin/settings/user.json b/public/language/es/admin/settings/user.json index 8e231827ca..395ae98b58 100644 --- a/public/language/es/admin/settings/user.json +++ b/public/language/es/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/es/email.json b/public/language/es/email.json index 6bb5dd06f8..ed986ea853 100644 --- a/public/language/es/email.json +++ b/public/language/es/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "La notificación de este mensaje se te ha enviado debido a tus ajustes de subscripción.", "test.text1": "Este es un email de prueba para verificar que el envío de email está ajustado correctamente para tu NodeBB", "unsub.cta": "Haz click aquí para modificar los ajustes.", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "¡Gracias!" } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index efd4d26307..f4d5c8fdef 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -30,6 +30,7 @@ "password-too-long": "Contraseña muy corta", "user-banned": "Usuario baneado", "user-banned-reason": "Lo siento, esta cuenta ha sido baneada ( Razon: %1 )", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Lo sentimos, es necesario que esperes %1 segundo(s) antes poder hacer tu primera publicación", "blacklisted-ip": "Lo sentimos, tu dirección IP ha sido baneada de esta comunidad. Si crees que debe de haber un error, por favor contacte con un administrador.", "ban-expiry-missing": "Por favor pon una fecha de fin del ban", @@ -104,7 +105,7 @@ "chat-disabled": "El sistema de chat está deshabilitado", "too-many-messages": "Has enviado demasiados mensajes, por favor espera un poco.", "invalid-chat-message": "Mensaje de Chat inválido", - "chat-message-too-long": "Mensaje de Chat es demasiado largo", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "No tienes permiso para editar este mensaje", "cant-remove-last-user": "No puedes eliminar el último usuario", "cant-delete-chat-message": "No tienes permiso para eliminar este mensaje", diff --git a/public/language/es/flags.json b/public/language/es/flags.json new file mode 100644 index 0000000000..012d3181b0 --- /dev/null +++ b/public/language/es/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Estado", + "reporter": "Reportador", + "reported-at": "Reportado en", + "description": "Descripción", + "no-flags": "Yeah! No se encontraron indicadores", + "assignee": "Asignado", + "update": "Actualizar", + "updated": "Actualizado", + "target-purged": "El contenido al que se refiere este indicador ha sido purgado y ya no está disponible.", + + "quick-filters": "Filtros rapidos", + "filter-active": "Hay uno o más filtros activos en esta lista de indicadores.", + "filter-reset": "Quitar filtros", + "filters": "Opciones de filtros", + "filter-reporterId": "UID del reportador", + "filter-targetUid": "Indicador UID", + "filter-type": "Tipo de indicador", + "filter-type-all": "Todo el contenido", + "filter-type-post": "Post", + "filter-state": "estado", + "filter-assignee": "UID asignado", + "filter-cid": "Categoria", + "filter-quick-mine": "Asignado a mí", + "filter-cid-all": "Todas las categorias", + "apply-filters": "Aplicar filtros", + + "quick-links": "Links rapidos", + "flagged-user": "Usuario marcado", + "view-profile": "Ver perfil", + "start-new-chat": "Empezar nuevo chat", + "go-to-target": "Ver objetivo marcado", + + "user-view": "Ver perfil", + "user-edit": "Editar perfil", + + "notes": "Marcar notas", + "add-note": "Añadir nota", + "no-notes": "No hay notas compartidas", + + "history": "Historico de marcadores", + "back": "Volver a la lista de marcadores", + "no-history": "No hay registro de marcadores", + + "state-all": "Todos los estados", + "state-open": "Nuevo/Abrir", + "state-wip": "Trabajo en proceso", + "state-resolved": "Resuelto", + "state-rejected": "Rechazado", + "no-assignee": "Sin asignar", + "note-added": "Nota añadida", + + "modal-title": "Reportar contenido inapropiado", + "modal-body": "Por favor especifica tu razón para marcar %1 %2 para revisar. Alternativamente, usa una de los botones de reporte rápido si corresponde.", + "modal-reason-spam": "Correo no deseado", + "modal-reason-offensive": "Ofensivo", + "modal-reason-custom": "Razón para reportar este contenido...", + "modal-submit": "Enviar reporte", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/es/modules.json b/public/language/es/modules.json index c373361c97..ce157b6c08 100644 --- a/public/language/es/modules.json +++ b/public/language/es/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 meses", "chat.delete_message_confirm": "¿Estás seguro de que deseas eliminar este mensaje?", "chat.add-users-to-room": "Añadir usuarios a la sala", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Crear", "composer.show_preview": "Ver Previsualización", "composer.hide_preview": "Ocultar Previsualización", diff --git a/public/language/es/user.json b/public/language/es/user.json index ed9d53fa1e..cb3779855d 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "El nombre de usuario que has solicitada ya está siendo usado, por tanto lo hemos alterado ligeramente. Ahora eres conocido como %1.", "password_same_as_username": "Tu Constraseña es igual al nombre de Usuario, por favor seleccione otra Constraseña.", "password_same_as_email": "Tu contraseña es igual que tu dirección de correo, por favor elige otra contraseña.", + "weak_password": "Weak password.", "upload_picture": "Subir foto", "upload_a_picture": "Subir una foto", "remove_uploaded_picture": "Borrar Imagen subida", diff --git a/public/language/et/admin/advanced/database.json b/public/language/et/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/et/admin/advanced/database.json +++ b/public/language/et/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/et/admin/manage/flags.json b/public/language/et/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/et/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/et/admin/manage/groups.json b/public/language/et/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/et/admin/manage/groups.json +++ b/public/language/et/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/et/admin/settings/advanced.json b/public/language/et/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/et/admin/settings/advanced.json +++ b/public/language/et/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/et/admin/settings/post.json b/public/language/et/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/et/admin/settings/post.json +++ b/public/language/et/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/et/admin/settings/user.json b/public/language/et/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/et/admin/settings/user.json +++ b/public/language/et/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/et/email.json b/public/language/et/email.json index 03fa17fc9f..38d3a3bb82 100644 --- a/public/language/et/email.json +++ b/public/language/et/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "See postituse teavitus on saadetud teile tellimuse seadistuse tõttu.", "test.text1": "See on test e-mail kinnitamaks, et emailer on korrektselt seadistatud sinu NodeBB jaoks.", "unsub.cta": "Vajuta siia, et muuta neid seadeid", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Aitäh!" } \ No newline at end of file diff --git a/public/language/et/error.json b/public/language/et/error.json index 41087e9e52..79979ceb3a 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -30,6 +30,7 @@ "password-too-long": "Parool liiga pikk", "user-banned": "Kasutaja bannitud", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Vabandust, te peate ootama %1 sekund(it) enne esimese postituse loomist.", "blacklisted-ip": "Vabandust! Sinu IP-aadress on siin kogukonnas keelatud. Kui arvad, et see on eksitus, palun leia kontakti administraatoriga.", "ban-expiry-missing": "Palun sisesta keelu lõpukuupäev", @@ -104,7 +105,7 @@ "chat-disabled": "Vestlus süsteem keelatud", "too-many-messages": "Oled saatnud liiga palju sõnumeid, oota natukene.", "invalid-chat-message": "Vigane vestluse sõnum", - "chat-message-too-long": "Vestluse sõnum on liiga pikk", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Sul ei ole lubatud antud sõnumit muuta", "cant-remove-last-user": "Sa ei saa viimast kasutajat eemaldada", "cant-delete-chat-message": "Sul ei ole lubatud antud sõnumit kustutada", diff --git a/public/language/et/flags.json b/public/language/et/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/et/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/et/modules.json b/public/language/et/modules.json index e844e2088e..35c5f88ef8 100644 --- a/public/language/et/modules.json +++ b/public/language/et/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Kuud", "chat.delete_message_confirm": "Oled kindel, et soovid selle sõnumi kustutada?", "chat.add-users-to-room": "Lisa kasutajaid ruumi", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Koosta", "composer.show_preview": "Kuva eelvaadet", "composer.hide_preview": "Peida eelvaade", diff --git a/public/language/et/user.json b/public/language/et/user.json index fe708b751e..ee6f98ef5b 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Kasutajanimi mida soovisid, ei olnud saadaval, seeg muutsime seda natukene. Sinu uus kasutajanimi on nüüd: %1", "password_same_as_username": "Su parool kattub su kasutajanimega, palun vali mõni muu parool.", "password_same_as_email": "Su parool kattub su e-mailiga, palun vali mõni muu parool.", + "weak_password": "Weak password.", "upload_picture": "Laadi pilt", "upload_a_picture": "Lae pilt üles", "remove_uploaded_picture": "Eemalda üleslaetud pilt", diff --git a/public/language/fa-IR/admin/advanced/database.json b/public/language/fa-IR/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/fa-IR/admin/advanced/database.json +++ b/public/language/fa-IR/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/fa-IR/admin/manage/flags.json b/public/language/fa-IR/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/fa-IR/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/fa-IR/admin/manage/groups.json b/public/language/fa-IR/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/fa-IR/admin/manage/groups.json +++ b/public/language/fa-IR/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/fa-IR/admin/settings/advanced.json b/public/language/fa-IR/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/fa-IR/admin/settings/advanced.json +++ b/public/language/fa-IR/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/fa-IR/admin/settings/post.json b/public/language/fa-IR/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/fa-IR/admin/settings/post.json +++ b/public/language/fa-IR/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/fa-IR/admin/settings/user.json b/public/language/fa-IR/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/fa-IR/admin/settings/user.json +++ b/public/language/fa-IR/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/fa-IR/email.json b/public/language/fa-IR/email.json index 121cfadc55..b040de56ee 100644 --- a/public/language/fa-IR/email.json +++ b/public/language/fa-IR/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "این اطلاعیه ی پستی که برای شما فرستاده شده به علت تنظیمات اشترک شماست.", "test.text1": "این یک ایمیل امتحانی جهت تایید اینکه فرستنده ایمیل برای انجمن NodeBB شما به درستی تنظیم و نصب شده است", "unsub.cta": "برای ویرایش آن تنظیمات اینجا کلیک کنید", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "سپاس!" } \ No newline at end of file diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json index b0bfe33133..92eff6f85f 100644 --- a/public/language/fa-IR/error.json +++ b/public/language/fa-IR/error.json @@ -30,6 +30,7 @@ "password-too-long": "کلمه عبور بسیار طولانیست", "user-banned": "کاربر اخراج شد", "user-banned-reason": "با عرض پوزش، این حساب کاربری از انجمن اخراج شده است (دلیل: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "با عرض پوزش، شما باید %1 ثانیه پیش از فرستادن پست نخست خود صبر کنید", "blacklisted-ip": "با عرض پوزش فراوان، نشانی آی پی شما در این انجمن مسدود شده است، اگر فکر می‌کنید اشتباهی رخ داده با مدیریت انجمن تماس بگیرید.", "ban-expiry-missing": "لطفا تاریخ پایان برای این مسدود کردن ارائه دهید", @@ -104,7 +105,7 @@ "chat-disabled": "سیستم گفتمان غیرفعال شده است", "too-many-messages": "شما پیامهای خیلی زیادی فرستاده اید، لطفا مدتی صبر نمایید", "invalid-chat-message": "پیام نامعتبر", - "chat-message-too-long": "پیام طولانی تر از حد مجاز است", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "شما اجازه ی ویرایش این پیام را ندارید", "cant-remove-last-user": "شما نمی توانید آخرین کاربر را حذف کنید", "cant-delete-chat-message": "شما اجازه حذف این پیام را ندارید.", diff --git a/public/language/fa-IR/flags.json b/public/language/fa-IR/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/fa-IR/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/fa-IR/modules.json b/public/language/fa-IR/modules.json index 5756198c40..0031f86bd2 100644 --- a/public/language/fa-IR/modules.json +++ b/public/language/fa-IR/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 ماه", "chat.delete_message_confirm": "آیا مطمئن هستید که می خواهید این پیام را حذف کنید؟", "chat.add-users-to-room": "اضافه کردن کاربر به این گفتگو", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "ارسال", "composer.show_preview": "نمایش پیش‌نمایش", "composer.hide_preview": "مخفی کردن پیش‌نمایش", diff --git a/public/language/fa-IR/user.json b/public/language/fa-IR/user.json index c053582c92..b2d3fc29a4 100644 --- a/public/language/fa-IR/user.json +++ b/public/language/fa-IR/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "نام کاربری درخواستی شما در حال حاضر گرفته شده است، بنابراین ما آن را کمی تغییر داده‌ایم. شما هم‌اکنون با نام %1null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/fi/admin/settings/post.json b/public/language/fi/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/fi/admin/settings/post.json +++ b/public/language/fi/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/fi/admin/settings/user.json b/public/language/fi/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/fi/admin/settings/user.json +++ b/public/language/fi/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/fi/email.json b/public/language/fi/email.json index 105dc065a1..33ba62f68d 100644 --- a/public/language/fi/email.json +++ b/public/language/fi/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", "unsub.cta": "Click here to alter those settings", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/fi/error.json b/public/language/fi/error.json index 072e879a4a..75e5d3609b 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "Käyttäjä on estetty", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Anteeksi, mutta sinun täytyy odottaa %1 sekunti(a) ennen sinun ensimmäisen viestin lähettämistä", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Keskustelujärjestelmä on pois käytöstä", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Virheellinen keskusteluviesti", - "chat-message-too-long": "Keskusteluviesti on liian pitkä", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/fi/flags.json b/public/language/fi/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/fi/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json index 880e41a742..95ae6fc34e 100644 --- a/public/language/fi/modules.json +++ b/public/language/fi/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 kuukautta", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/fi/user.json b/public/language/fi/user.json index 38f88a106a..3651e69e04 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Pyytämäsi käyttäjänimi oli jo varattu, joten muutimme sitä hieman. Käyttäjänimesi on siis nyt %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Lataa kuva", "upload_a_picture": "Lataa kuva", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/fr/admin/advanced/database.json b/public/language/fr/admin/advanced/database.json index 31ec94e572..ee2103cf4f 100644 --- a/public/language/fr/admin/advanced/database.json +++ b/public/language/fr/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 octets", "x-mb": "%1 Mo", + "x-gb": "%1 gb", "uptime-seconds": "Disponibilité en secondes", "uptime-days": "Disponibilité en jours", diff --git a/public/language/fr/admin/manage/flags.json b/public/language/fr/admin/manage/flags.json deleted file mode 100644 index 7245441880..0000000000 --- a/public/language/fr/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Signalements par jours", - "by-user": "Signalements par utilisateur", - "by-user-search": "Rechercher une sujet signalé par nom d'utilisateur", - "category": "Catégorie", - "sort-by": "Trier par", - "sort-by.most-flags": "Les plus signalés", - "sort-by.most-recent": "Les plus récents", - "search": "Rechercher", - "dismiss-all": "Tout classer", - "none-flagged": "Aucun sujet signalé !", - "posted-in": "Posté dans 1%", - "read-more": "Lire la suite", - "flagged-x-times": "Ce sujet a été signalé %1 fois :", - "dismiss": "Classer ce signalement", - "delete-post": "Supprimer le message", - - "alerts.confirm-delete-post": "Voulez vous réellement supprimer ce message ?" -} \ No newline at end of file diff --git a/public/language/fr/admin/manage/groups.json b/public/language/fr/admin/manage/groups.json index 82c389292d..ec1898fbcb 100644 --- a/public/language/fr/admin/manage/groups.json +++ b/public/language/fr/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Nom du groupe", "description": "Description du groupe", + "member-count": "Member Count", "system": "Groupe système", "edit": "Éditer", "search-placeholder": "Rechercher", diff --git a/public/language/fr/admin/settings/advanced.json b/public/language/fr/admin/settings/advanced.json index cdd7373c15..42fa98183f 100644 --- a/public/language/fr/admin/settings/advanced.json +++ b/public/language/fr/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Définissez ALLOW-FROM pour afficher NodeBB dans un iFrame", "headers.powered-by": "Personnaliser l'en-tête \"Propulsé par\" envoyé par NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "Pour interdire l'accès à tous les sites, laisser vide ou définissez comme null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "\nAccess-Control-Allow-Methods", "headers.acah": "\nAccess-Control-Allow-Headers", "traffic-management": "Gestion du trafic", diff --git a/public/language/fr/admin/settings/post.json b/public/language/fr/admin/settings/post.json index fdec928a98..5fb72189c8 100644 --- a/public/language/fr/admin/settings/post.json +++ b/public/language/fr/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Paramètres des messages non lus", "unread.cutoff": "Nombre de jours pour les messages non-lus", "unread.min-track-last": "Nombre minimum de messages dans le sujet avant de garder en mémoire le dernier message lu", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Paramètres de signature", "signature.disable": "Désactiver les signatures", "signature.no-links": "Désactiver les liens en signature", diff --git a/public/language/fr/admin/settings/user.json b/public/language/fr/admin/settings/user.json index c37e8adbee..d6c9fb0c23 100644 --- a/public/language/fr/admin/settings/user.json +++ b/public/language/fr/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Longueur minimum du nom d'utilisateur", "max-username-length": "Longueur maxmum du nom d'utilisateur", "min-password-length": "Longueur minimum du mot de passe", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Longueur maximum du À propos de moi", "terms-of-use": "Conditions générales d'utilisation du forum (Laisser vide pour désactiver)", "user-search": "Rechercher un utilisateur", diff --git a/public/language/fr/email.json b/public/language/fr/email.json index 477db2a967..73634e14f3 100644 --- a/public/language/fr/email.json +++ b/public/language/fr/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "La notification de ce message vous a été envoyé en raison de vos paramètres d'abonnement.", "test.text1": "Ceci est un e-mail de test pour vérifier que l'e-mailer est correctement configuré pour NodeBB.", "unsub.cta": "Cliquez ici pour modifier ces paramètres", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Merci !" } \ No newline at end of file diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 8bf9995e41..a158d6f83b 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -30,6 +30,7 @@ "password-too-long": "Mot de passe trop long", "user-banned": "Utilisateur banni", "user-banned-reason": "Désolé, ce compte a été banni (Raison : %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Désolé, vous devez attendre encore %1 seconde(s) avant d'envoyer votre premier message", "blacklisted-ip": "Désolé, votre adresse IP a été bannie de cette communauté. Si vous pensez que c'est une erreur, veuillez contacter un administrateur.", "ban-expiry-missing": "Veuillez entrer une date de fin de banissement.", @@ -104,7 +105,7 @@ "chat-disabled": "Système de chat désactivé", "too-many-messages": "Vous avez envoyé trop de messages, veuillez patienter un instant.", "invalid-chat-message": "Message de Chat invalide", - "chat-message-too-long": "Le message de Chat est trop long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Vous n'avez pas l'autorisation de modifier ce message", "cant-remove-last-user": "Vous ne pouvez pas supprimer le dernier utilisateur", "cant-delete-chat-message": "Vous n'avez pas l'autorisation de supprimer ce message", diff --git a/public/language/fr/flags.json b/public/language/fr/flags.json new file mode 100644 index 0000000000..c4d031cca4 --- /dev/null +++ b/public/language/fr/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Etat", + "reporter": "Rapporteur", + "reported-at": "Reporté à", + "description": "Description", + "no-flags": "Hourra ! Aucun signalement trouvé.", + "assignee": "Assigné", + "update": "Mise à jour", + "updated": "Mis à jour", + "target-purged": "Le contenu référencé par ce signalement a été supprimé et n'est plus accessible", + + "quick-filters": "Filtres rapides", + "filter-active": "Il y a un ou plusieurs filtres actifs dans cette liste de signalements", + "filter-reset": "Supprimer les filtres", + "filters": "Options de filtre", + "filter-reporterId": "UID du reporteur", + "filter-targetUid": "UID signalé", + "filter-type": "Type de signalement", + "filter-type-all": "Tout le contenu", + "filter-type-post": "Message", + "filter-state": "Etat", + "filter-assignee": "UID assigné", + "filter-cid": "Catégorie", + "filter-quick-mine": "Assigné à moi", + "filter-cid-all": "Toutes les catégories", + "apply-filters": "Appliquer les filtres", + + "quick-links": "Permaliens", + "flagged-user": "Utilisateurs signalés", + "view-profile": "Voir le profil", + "start-new-chat": "Démarrer un nouveau Chat", + "go-to-target": "Voir le signalement cible", + + "user-view": "Voir le profil", + "user-edit": "Éditer le profil", + + "notes": "Notes de signalement", + "add-note": "Ajouter une note", + "no-notes": "aucune note partagée", + + "history": "Historiques des signalements", + "back": "Revenir à la liste des signalements", + "no-history": "aucun historique de signalements", + + "state-all": "Tous les états", + "state-open": "Nouveau/Ouvert", + "state-wip": "En cours", + "state-resolved": "Résolu", + "state-rejected": "Rejeté", + "no-assignee": "Non assigné", + "note-added": "Note ajoutée", + + "modal-title": "Signaler un contenu inapproprié", + "modal-body": "Veuillez spécifier votre raison de signaler %1 %2 pour une révision. Vous pouvez utiliser un des boutons de report rapide si c'est plus approprié", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Choquant", + "modal-reason-custom": "Motif du signalement...", + "modal-submit": "Soumettre le signalement", + "modal-submit-success": "Le contenu a été soumis pour examen." +} \ No newline at end of file diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json index c41811fd84..5f3c253db7 100644 --- a/public/language/fr/modules.json +++ b/public/language/fr/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Mois", "chat.delete_message_confirm": "Êtes-vous sûr de vouloir supprimer ce message ?", "chat.add-users-to-room": "Ajouter des participants", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Écrire", "composer.show_preview": "Afficher l'aperçu", "composer.hide_preview": "Masquer l'aperçu", diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 0895c10716..f09cefe60b 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Le nom d'utilisateur désiré est déjà utilisé, nous l'avons donc légèrement modifié. Vous êtes maintenant connu comme %1", "password_same_as_username": "Votre mot de passe est identique à votre nom d'utilisateur. Veuillez en choisir un autre.", "password_same_as_email": "Votre mot de passe est identique à votre adresse email. Veuillez en choisir un autre.", + "weak_password": "Weak password.", "upload_picture": "Envoyer l'image", "upload_a_picture": "Envoyer une image", "remove_uploaded_picture": "Supprimer l'image envoyée", diff --git a/public/language/gl/admin/advanced/database.json b/public/language/gl/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/gl/admin/advanced/database.json +++ b/public/language/gl/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/gl/admin/manage/flags.json b/public/language/gl/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/gl/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/gl/admin/manage/groups.json b/public/language/gl/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/gl/admin/manage/groups.json +++ b/public/language/gl/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/gl/admin/settings/advanced.json b/public/language/gl/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/gl/admin/settings/advanced.json +++ b/public/language/gl/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/gl/admin/settings/post.json b/public/language/gl/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/gl/admin/settings/post.json +++ b/public/language/gl/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/gl/admin/settings/user.json b/public/language/gl/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/gl/admin/settings/user.json +++ b/public/language/gl/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/gl/email.json b/public/language/gl/email.json index 7bcedbf37e..0aee60f82d 100644 --- a/public/language/gl/email.json +++ b/public/language/gl/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Esta notificación de mensaxe foiche enviada polas túas opcións de subscrición.", "test.text1": "Esta é unha mensaxe de proba para verificar que o envío de correo está configurado correctamente para o seu NodeBB.", "unsub.cta": "Pica aquí para cambiar os axustes", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Grazas!" } \ No newline at end of file diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 86c84601f6..f6fd1bca9b 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -30,6 +30,7 @@ "password-too-long": "Contrasinal moi longa", "user-banned": "Usuario expulsado", "user-banned-reason": "Desculpa, esta conta foi baneada (Razón: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Desculpa, agarda %1 second(s) antes de facer a túa primeira publicación.", "blacklisted-ip": "Sentímolo, o teu enderezo IP foi baneado desta comunidade. Se crees que se debe a un erro, por favor, contacte cun administrador.", "ban-expiry-missing": "Por favor, engade unha data de fin do ban", @@ -104,7 +105,7 @@ "chat-disabled": "Charlas desactivadas", "too-many-messages": "Estás a enviar moitas mensaxes, por favor, agarda un anaco. ", "invalid-chat-message": "Mensaxe inválida", - "chat-message-too-long": "Mensaxe moi longa", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Non tes permitido editar esta mensaxe.", "cant-remove-last-user": "Non podes quitar o último usuario", "cant-delete-chat-message": "Non tes permitido borrar esta mensaxe.", diff --git a/public/language/gl/flags.json b/public/language/gl/flags.json new file mode 100644 index 0000000000..0fda38ff5a --- /dev/null +++ b/public/language/gl/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Estado", + "reporter": "Reportador", + "reported-at": "Reportado en", + "description": "Descripción", + "no-flags": "Un licorca, que non hai nada marcado para revisión.", + "assignee": "Encargado", + "update": "Actualizar", + "updated": "Actualizado", + "target-purged": "O contido marcado foi purgado e xa non está dispoñible", + + "quick-filters": "Filtros rápidos", + "filter-active": "Hai un ou máis filtros na lista de avisos", + "filter-reset": "Eliminar filtros", + "filters": "Filtrar opcións", + "filter-reporterId": "UID do reportador", + "filter-targetUid": "UID marcada", + "filter-type": "Tipo de aviso", + "filter-type-all": "Todo o contido", + "filter-type-post": "Publicar", + "filter-state": "Estado", + "filter-assignee": "UID do encargado", + "filter-cid": "Categoría", + "filter-quick-mine": "Asignado a min", + "filter-cid-all": "Tódalas categorías", + "apply-filters": "Aplicar filtros", + + "quick-links": "Ligazóns rápidas", + "flagged-user": "Usuario marcado", + "view-profile": "Ver perfil", + "start-new-chat": "Comezar novo chat", + "go-to-target": "Ver contido marcado", + + "user-view": "Ver perfil", + "user-edit": "Editar perfil", + + "notes": "Notas do aviso", + "add-note": "Engadir nota", + "no-notes": "Ningunha nota foi compartida", + + "history": "Historial de avisos", + "back": "Voltar á lista de avisos", + "no-history": "Non hai historial de avisos", + + "state-all": "Tódolos estados", + "state-open": "Novo/Abrir", + "state-wip": "Traballo en progreso", + "state-resolved": "Resolto", + "state-rejected": "Rexeitado", + "no-assignee": "Non asignado", + "note-added": "Nota engadida", + + "modal-title": "Reportar Contido Inapropiado", + "modal-body": "Por favor, especifique o seu motivo para marcar %1 %2 para revisión. Alternativamente, empregue un dos botóns de reporte rápido se fose pertinente.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Ofensivo", + "modal-reason-custom": "Motivo para reportar este contido...", + "modal-submit": "Enviar Reporte", + "modal-submit-success": "Contido marcado para moderación" +} \ No newline at end of file diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json index 0a9c2cca4f..8172d7dae3 100644 --- a/public/language/gl/modules.json +++ b/public/language/gl/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Meses", "chat.delete_message_confirm": "Estás seguro de que desexas eliminar esta mensaxe?", "chat.add-users-to-room": "Engadir usuarios á sala", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Elaborar", "composer.show_preview": "Amosar vista previa", "composer.hide_preview": "Agochar vista previa", diff --git a/public/language/gl/user.json b/public/language/gl/user.json index 0405ac4e05..4019fabb5c 100644 --- a/public/language/gl/user.json +++ b/public/language/gl/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Ese nome de usuario xa estaba collido, así que o modificamos lixeiramente. Agora o teu nome é %1 ", "password_same_as_username": "O teu contrasinal e o teu nome de usuario son os mesmos, por favor, escolle outro contrasinal.", "password_same_as_email": "O teu contrasinal é igual que o teu enderezo electrónico, por favor, escolle outro contrasinal.", + "weak_password": "Weak password.", "upload_picture": "Subir foto", "upload_a_picture": "Subir unha foto", "remove_uploaded_picture": "Borrar unha foto subida", diff --git a/public/language/he/admin/advanced/database.json b/public/language/he/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/he/admin/advanced/database.json +++ b/public/language/he/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/he/admin/manage/flags.json b/public/language/he/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/he/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/he/admin/manage/groups.json b/public/language/he/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/he/admin/manage/groups.json +++ b/public/language/he/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/he/admin/settings/advanced.json b/public/language/he/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/he/admin/settings/advanced.json +++ b/public/language/he/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/he/admin/settings/post.json b/public/language/he/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/he/admin/settings/post.json +++ b/public/language/he/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/he/admin/settings/user.json +++ b/public/language/he/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/he/email.json b/public/language/he/email.json index 48ff8f4a50..aa2579e87a 100644 --- a/public/language/he/email.json +++ b/public/language/he/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "התראת הפוסט הזו נשלחה אליך על-פי הגדרות החשבון שלך.", "test.text1": "זהו אימייל ניסיון על מנת לוודא שהגדרות המייל בוצעו כהלכה בהגדרות NodeBB.", "unsub.cta": "לחץ כאן לשנות הגדרות אלו", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "תודה!" } \ No newline at end of file diff --git a/public/language/he/error.json b/public/language/he/error.json index b883e71aff..16430c13d1 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -30,6 +30,7 @@ "password-too-long": "הסיסמה ארוכה מדי", "user-banned": "המשתמש מושעה", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "אנא המתן %1 שניות לפני פרסום ההודעה", "blacklisted-ip": "מצטערים, אך הורחקת מקהילה זו. אם הנך סבור שמדובר בטעות, אנא צור קשר עם מנהלי הקהילה.", "ban-expiry-missing": "אנא ספק תאריך סיום להרחקה זו.", @@ -104,7 +105,7 @@ "chat-disabled": "מערכת הצ'אט לא פעילה", "too-many-messages": "שלחת יותר מדי הודעות, אנא המתן לזמן מה.", "invalid-chat-message": "הודעת צ'אט לא תקינה", - "chat-message-too-long": "הודעת הצ'אט ארוכה מדי", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "אתה לא רשאי לערוך הודעה זו", "cant-remove-last-user": "אינך יכול למחוק את המשתמש האחרון", "cant-delete-chat-message": "אתה לא רשאי למחוק הודעה זו", diff --git a/public/language/he/flags.json b/public/language/he/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/he/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/he/modules.json b/public/language/he/modules.json index 87f4a45685..6e41dad53f 100644 --- a/public/language/he/modules.json +++ b/public/language/he/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 חודשים", "chat.delete_message_confirm": "האם אתה בטוח שברצונך למחוק הודעה זו?", "chat.add-users-to-room": "הוסף משתמשים לצ'אט", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "צור", "composer.show_preview": "הצג תצוגה מקדימה", "composer.hide_preview": "הסתר תצוגה מקדימה", diff --git a/public/language/he/user.json b/public/language/he/user.json index 0b96a2075e..d5ad68e33b 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "שם המשתמש שבחרת כבר תפוס, אז שינינו אותו מעט. שם המשתמש שלך כעת הוא %1", "password_same_as_username": "הסיסמה שלך זהה לשם המשתמש, אנא בחר סיסמה שונה.", "password_same_as_email": "הסיסמה שלך זהה לכתובת המייל שלך, אנא בחר סיסמה שונה.", + "weak_password": "Weak password.", "upload_picture": "העלה תמונה", "upload_a_picture": "העלה תמונה", "remove_uploaded_picture": "מחק את התמונה שהועלתה", diff --git a/public/language/hu/admin/advanced/database.json b/public/language/hu/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/hu/admin/advanced/database.json +++ b/public/language/hu/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/hu/admin/manage/flags.json b/public/language/hu/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/hu/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/hu/admin/manage/groups.json b/public/language/hu/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/hu/admin/manage/groups.json +++ b/public/language/hu/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/hu/admin/settings/advanced.json b/public/language/hu/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/hu/admin/settings/advanced.json +++ b/public/language/hu/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/hu/admin/settings/post.json b/public/language/hu/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/hu/admin/settings/post.json +++ b/public/language/hu/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/hu/admin/settings/user.json b/public/language/hu/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/hu/admin/settings/user.json +++ b/public/language/hu/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/hu/email.json b/public/language/hu/email.json index 67f241f750..548aba2ed3 100644 --- a/public/language/hu/email.json +++ b/public/language/hu/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Ez a hozzászólás-értesítés a feliratkozási beállításaid miatt lett kiküldve.", "test.text1": "Ez egy teszt levél, ami által ellenőrizzük, hogy a levelező helyesen lett beállítva a NodeBB-ben.", "unsub.cta": "Kattintson ide a beállítások módosításához", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Köszönjük!" } \ No newline at end of file diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 499fba5713..2741e13087 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "Kitiltott felhasználó", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "Túl sok üzenetet küldtél, kérlek várj egy picit.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/hu/flags.json b/public/language/hu/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/hu/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json index 15a176a383..2421ff6436 100644 --- a/public/language/hu/modules.json +++ b/public/language/hu/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 hónap", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/hu/user.json b/public/language/hu/user.json index 2870533d8f..c1e48843a5 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "A kívánt felhasználónév már foglalt, így változtatnunk kellett rajta egy kicsit. Mostantól %1 név alatt vagy ismert.", "password_same_as_username": "A jelszavad megegyezik a felhasználóneveddel, kérlek válassz másik jelszót.", "password_same_as_email": "A jelszavad megegyezik az e-mail címeddel, kérlek válassz másik jelszót.", + "weak_password": "Weak password.", "upload_picture": "Kép feltöltése", "upload_a_picture": "Egy kép feltöltése", "remove_uploaded_picture": "Feltöltött kép eltávolítása", diff --git a/public/language/id/admin/advanced/database.json b/public/language/id/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/id/admin/advanced/database.json +++ b/public/language/id/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/id/admin/manage/flags.json b/public/language/id/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/id/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/id/admin/manage/groups.json b/public/language/id/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/id/admin/manage/groups.json +++ b/public/language/id/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/id/admin/settings/advanced.json b/public/language/id/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/id/admin/settings/advanced.json +++ b/public/language/id/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/id/admin/settings/post.json b/public/language/id/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/id/admin/settings/post.json +++ b/public/language/id/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/id/admin/settings/user.json b/public/language/id/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/id/admin/settings/user.json +++ b/public/language/id/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/id/email.json b/public/language/id/email.json index 9e79021f89..9289a89134 100644 --- a/public/language/id/email.json +++ b/public/language/id/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "Ini hanya email percobaan untuk menverifkasi pengiriman email telah diatur oleh NodeBB secara benar", "unsub.cta": "Klik di sini untuk mengubah pengaturan-pengaturan tersebut.", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Terima kasih!" } \ No newline at end of file diff --git a/public/language/id/error.json b/public/language/id/error.json index 716db0823d..18c53d69f5 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "Pengguna dibanned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/id/flags.json b/public/language/id/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/id/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/id/modules.json b/public/language/id/modules.json index 681551cb0b..194e9c7428 100644 --- a/public/language/id/modules.json +++ b/public/language/id/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Bulan", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/id/user.json b/public/language/id/user.json index d039f98129..60cbd283fe 100644 --- a/public/language/id/user.json +++ b/public/language/id/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Nama pengguna yang kamu inginkan telah diambil, jadi kami merubahnya sedikit. Kamu saat ini dikenal sebagai %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Unggah gambar/foto", "upload_a_picture": "Unggah sebuah gambar/foto", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/it/admin/advanced/database.json b/public/language/it/admin/advanced/database.json index 53404f0915..7430868748 100644 --- a/public/language/it/admin/advanced/database.json +++ b/public/language/it/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in secondi", "uptime-days": "Uptime in giorni", diff --git a/public/language/it/admin/manage/flags.json b/public/language/it/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/it/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/it/admin/manage/groups.json b/public/language/it/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/it/admin/manage/groups.json +++ b/public/language/it/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/it/admin/settings/advanced.json b/public/language/it/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/it/admin/settings/advanced.json +++ b/public/language/it/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/it/admin/settings/post.json b/public/language/it/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/it/admin/settings/post.json +++ b/public/language/it/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/it/admin/settings/user.json b/public/language/it/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/it/admin/settings/user.json +++ b/public/language/it/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/it/email.json b/public/language/it/email.json index f30f93b9f5..1cf2f5ad7b 100644 --- a/public/language/it/email.json +++ b/public/language/it/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Questo post ti è stato notificato in base alle tue impostazioni di sottoscrizione.", "test.text1": "Questa è una email di test per verificare che il servizio di invio email è configurato correttamente sul tuo NodeBB.", "unsub.cta": "Clicca qui per modificare queste impostazioni", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Grazie!" } \ No newline at end of file diff --git a/public/language/it/error.json b/public/language/it/error.json index 0aba02e938..e87e872b70 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password troppo lunga", "user-banned": "Utente bannato", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Devi attendere %1 secondi prima di creare il tuo primo post", "blacklisted-ip": "Purtroppo il tuo indirizzo IP è stato bannato da questa community. Se credi che ci sia stato un errore contatta un amministratore.", "ban-expiry-missing": "Per favore fornisci una data finale per questo ban", @@ -104,7 +105,7 @@ "chat-disabled": "Il sistema di chat è stato disabilitato", "too-many-messages": "Hai inviato troppi messaggi, aspetta un attimo.", "invalid-chat-message": "Messaggio chat non valido", - "chat-message-too-long": "Il messaggio chat è troppo lungo", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Non ti è permesso di modificare questo messaggio", "cant-remove-last-user": "Non puoi rimuovere l'ultimo utente", "cant-delete-chat-message": "Non ti è permesso di eliminare questo messaggio", diff --git a/public/language/it/flags.json b/public/language/it/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/it/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/it/global.json b/public/language/it/global.json index 7b7fbae4c8..b0599c51d0 100644 --- a/public/language/it/global.json +++ b/public/language/it/global.json @@ -75,7 +75,7 @@ "norecenttopics": "Nessuna Discussione Recente", "recentposts": "Post Recenti", "recentips": "Indirizzi IP Recentemente Loggati", - "moderator_tools": "Moderator Tools", + "moderator_tools": "Strumenti di amministrazione", "away": "Non disponibile", "dnd": "Non disturbare", "invisible": "Invisibile", @@ -98,7 +98,7 @@ "upload": "Carica", "allowed-file-types": "Le estensioni permesse dei file sono %1", "unsaved-changes": "Hai delle modifiche non salvate. Sei sicuro che vuoi lasciare la pagina?", - "reconnecting-message": "Sembra che la tua connessione a %1 sia stata persa, per favore attenti mentre proviamo a riconnetterti.", + "reconnecting-message": "Sembra che la tua connessione a %1 sia stata persa, per favore attendi mentre proviamo a riconnetterti.", "play": "Play", "cookies.message": "Questo sito utilizza i cookie per garantirti la miglior esperienza di navigazione possibile", "cookies.accept": "Ho capito!", diff --git a/public/language/it/modules.json b/public/language/it/modules.json index 7e4e0fa0ce..5fcef5d582 100644 --- a/public/language/it/modules.json +++ b/public/language/it/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Mesi", "chat.delete_message_confirm": "Sei sicuro di voler eliminare questo messaggio?", "chat.add-users-to-room": "Aggiungi utenti alla stanza", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Componi", "composer.show_preview": "Visualizza Anteprima", "composer.hide_preview": "Nascondi Anteprima", diff --git a/public/language/it/user.json b/public/language/it/user.json index 9d5a88d4e0..8bbe9c5ea3 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -33,7 +33,7 @@ "chat": "Chat", "chat_with": "Continua la chat con %1", "new_chat_with": "Inizia una nuova chat con %1", - "flag-profile": "Flag Profile", + "flag-profile": "Flag Profilo", "follow": "Segui", "unfollow": "Smetti di seguire", "more": "Altro", @@ -60,13 +60,14 @@ "username_taken_workaround": "Il nome utente che hai richiesto era già stato utilizzato, quindi lo abbiamo modificato leggermente. Ora il tuo è %1", "password_same_as_username": "La tua password è uguale al tuo username, per piacere scegli un'altra password", "password_same_as_email": "La tua password sembra coincidere con la tua email, per favore fornisci un'altra password.", + "weak_password": "Weak password.", "upload_picture": "Carica foto", "upload_a_picture": "Carica una foto", "remove_uploaded_picture": "Elimina foto caricata", "upload_cover_picture": "Carica immagine di copertina", "remove_cover_picture_confirm": "Sei sicuro di voler eliminare l'immagine di copertina?", - "crop_picture": "Crop picture", - "upload_cropped_picture": "Crop and upload", + "crop_picture": "Ritaglia immagine", + "upload_cropped_picture": "Ritaglia e carica", "settings": "Impostazioni", "show_email": "Mostra la mia Email", "show_fullname": "Mostra il mio nome completo", @@ -129,7 +130,7 @@ "info.banned-no-reason": "Non è stata data nessuna motivazione.", "info.username-history": "Storico del nome utente", "info.email-history": "Storico dell'Email", - "info.moderation-note": "Moderation Note", - "info.moderation-note.success": "Moderation note saved", - "info.moderation-note.add": "Add note" + "info.moderation-note": "Nota di moderazione", + "info.moderation-note.success": "Nota di moderazione salvata", + "info.moderation-note.add": "Aggiungi nota" } \ No newline at end of file diff --git a/public/language/ja/admin/advanced/database.json b/public/language/ja/admin/advanced/database.json index 4dac0a3eab..26f19fa233 100644 --- a/public/language/ja/admin/advanced/database.json +++ b/public/language/ja/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "秒単位の稼働時間", "uptime-days": "日単位の稼働時間", diff --git a/public/language/ja/admin/extend/plugins.json b/public/language/ja/admin/extend/plugins.json index 4756c93c85..35159ce1df 100644 --- a/public/language/ja/admin/extend/plugins.json +++ b/public/language/ja/admin/extend/plugins.json @@ -5,7 +5,7 @@ "out-of-date": "期限切れ", "none-found": "プラグインが見つかりませんでした", "none-active": "アクティブなプラグインが見つかりませんでした", - "find-plugins": "プラグインが見つかりました", + "find-plugins": "プラグイン一覧", "plugin-search": "プラグインの検索", "plugin-search-placeholder": "プラグインを検索します...", diff --git a/public/language/ja/admin/general/dashboard.json b/public/language/ja/admin/general/dashboard.json index 86d2db6e52..7dcb9e74ec 100644 --- a/public/language/ja/admin/general/dashboard.json +++ b/public/language/ja/admin/general/dashboard.json @@ -15,13 +15,13 @@ "stats.all": "全て", "updates": "更新", - "running-version": "NodeBB v %1 を実行しています。", + "running-version": "NodeBB v %1 を実行しています。", "keep-updated": "常に最新のセキュリティパッチとバグ修正のためにNodeBBが最新であることを確認してください。", "up-to-date": "

あなたは最新の状態です。 ", "upgrade-available": "

新しいバージョン (v%1) がリリースされました。NodeBBのアップグレードを検討してください。

", - "prerelease-upgrade-available": "

これはNodeBBの旧リリースのバージョンです。新しいバージョン(v%1)がリリースされました。 NodeBBのアップグレードを検討してください。", + "prerelease-upgrade-available": "

これはNodeBBの旧リリースのバージョンです。新しいバージョン(v%1)がリリースされました。 NodeBBのアップグレードを検討してください。", "prerelease-warning": "

これはNodeBBのプレリリース版です。意図しないバグが発生することがあります。

", - "running-in-development": "Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator.", + "running-in-development": "フォーラムが開発モードで動作しています。フォーラムの動作が脆弱かもしれませんので、管理者に問い合わせてください。", "notices": "通知", "restart-not-required": "再起動は必要ありません", diff --git a/public/language/ja/admin/manage/flags.json b/public/language/ja/admin/manage/flags.json deleted file mode 100644 index 0b127a26ca..0000000000 --- a/public/language/ja/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "日付フラッグ", - "by-user": "ユーザーからのフラグ", - "by-user-search": "フラグを付けたユーザーを検索", - "category": "カテゴリ", - "sort-by": "並び順", - "sort-by.most-flags": "最も多いフラグ", - "sort-by.most-recent": "最も新しい", - "search": "検索", - "dismiss-all": "すべてを却下する", - "none-flagged": "フラグの立った投稿はありません!", - "posted-in": "%1に投稿されました", - "read-more": "続きを読む", - "flagged-x-times": "この投稿には%1回のフラグが設定されています(s):", - "dismiss": "このフラグを却下する", - "delete-post": "投稿を削除", - - "alerts.confirm-delete-post": "本当にこの投稿を削除しますか?" -} \ No newline at end of file diff --git a/public/language/ja/admin/manage/groups.json b/public/language/ja/admin/manage/groups.json index 56aaeaa1d4..c0f55920fd 100644 --- a/public/language/ja/admin/manage/groups.json +++ b/public/language/ja/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "グループ名", "description": "グループの説明", + "member-count": "Member Count", "system": "システムグループ", "edit": "編集", "search-placeholder": "検索", diff --git a/public/language/ja/admin/menu.json b/public/language/ja/admin/menu.json index 0f1be1b2a9..7408a1aa5d 100644 --- a/public/language/ja/admin/menu.json +++ b/public/language/ja/admin/menu.json @@ -7,7 +7,7 @@ "general/sounds": "サウンド", "general/social": "ソーシャル", - "section-manage": "メッセージ", + "section-manage": "管理", "manage/categories": "カテゴリ", "manage/tags": "タグ", "manage/users": "ユーザー", diff --git a/public/language/ja/admin/settings/advanced.json b/public/language/ja/admin/settings/advanced.json index f54cb6d4d7..f7ffbdbaf2 100644 --- a/public/language/ja/admin/settings/advanced.json +++ b/public/language/ja/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "NodeBBをインラインフレーム内に配置するようALLOW-FROMを設定する", "headers.powered-by": "NodeBBから送信された「Powered By」ヘッダーをカスタマイズする", "headers.acao": "アクセス-制御-有効-原点", - "headers.acao-help": "すべてのサイトへのアクセスを拒否するには空のままにするか、nullに設定します", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "アクセス-制御-有効-メソッド", "headers.acah": "アクセス-制御-有効-ヘッダー", "traffic-management": "トラフィック管理", diff --git a/public/language/ja/admin/settings/general.json b/public/language/ja/admin/settings/general.json index cd2480f642..dfce753a98 100644 --- a/public/language/ja/admin/settings/general.json +++ b/public/language/ja/admin/settings/general.json @@ -28,5 +28,5 @@ "outgoing-links": "外部サイトへのリンク", "outgoing-links.warning-page": "送信リンクの警告ページを使用", "search-default-sort-by": "デフォルトのソートを検索", - "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page" + "outgoing-links.whitelist": "警告ページをバイパスするためのホワイトリストへのドメイン" } \ No newline at end of file diff --git a/public/language/ja/admin/settings/post.json b/public/language/ja/admin/settings/post.json index b82142e0e2..2eea0ed739 100644 --- a/public/language/ja/admin/settings/post.json +++ b/public/language/ja/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "未読の設定", "unread.cutoff": "未読のカットオフ日", "unread.min-track-last": "最後に読み込みを行う前に追跡するスレッドの最小投稿数", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "署名の設定", "signature.disable": "署名を無効にする", "signature.no-links": "署名内のリンクを無効にする", diff --git a/public/language/ja/admin/settings/user.json b/public/language/ja/admin/settings/user.json index ee78fda0f4..1048a1863c 100644 --- a/public/language/ja/admin/settings/user.json +++ b/public/language/ja/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "ユーザー名の最小文字数", "max-username-length": "ユーザー名の最大文字数", "min-password-length": "パスワードの最小文字数", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "概要の最大文字数", "terms-of-use": "フォーラム利用規約(空白のままにしておくと無効になります)", "user-search": "ユーザーを検索", diff --git a/public/language/ja/email.json b/public/language/ja/email.json index 461b393f30..8dcf90bb5c 100644 --- a/public/language/ja/email.json +++ b/public/language/ja/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "この投稿の通知はあなたの申し込み設定により送られました。", "test.text1": "このメールはNodeBBのメーラー(emailer)が正しく設定されているか確認をするためのメールです。", "unsub.cta": "ここをクリックして設定を変更する", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "ありがとうございます!" } \ No newline at end of file diff --git a/public/language/ja/error.json b/public/language/ja/error.json index d01ac87df2..52df59a6d4 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -30,6 +30,7 @@ "password-too-long": "パスワードが長すぎます", "user-banned": "ユーザーは停止されています", "user-banned-reason": "申し訳ありませんが、このアカウントは停止されています。 (理由: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "申し訳ありません。登録後に投稿を行うには%1秒お待ち下さい。", "blacklisted-ip": "申し訳ありませんがあなたのIPアドレスは当コミュニティで停止されています。もし誤ったエラーだと思われる場合は管理者にお問い合わせください。", "ban-expiry-missing": "この停止の終了日を入力してください。", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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": "あなたはこのメッセージを削除する権限を持っていません。", diff --git a/public/language/ja/flags.json b/public/language/ja/flags.json new file mode 100644 index 0000000000..90a2d96569 --- /dev/null +++ b/public/language/ja/flags.json @@ -0,0 +1,60 @@ +{ + "state": "状態", + "reporter": "報告者", + "reported-at": "報告された", + "description": "説明", + "no-flags": "おめでとう!フラグは見つかりませんでした。", + "assignee": "譲受人", + "update": "更新", + "updated": "更新されました", + "target-purged": "このフラグが参照しているコンテンツは切り離されており、利用できません。", + + "quick-filters": "クイックフィルター", + "filter-active": "このフラグのリストには1つまたは複数のフィルタが有効です。", + "filter-reset": "フィルターを削除", + "filters": "フィルターオプション", + "filter-reporterId": "報告者のユーザーID", + "filter-targetUid": "フラグを立てたユーザーID", + "filter-type": "フラグの種類", + "filter-type-all": "すべてのコンテンツ", + "filter-type-post": "投稿", + "filter-state": "状態", + "filter-assignee": "譲受人のユーザーID", + "filter-cid": "カテゴリ", + "filter-quick-mine": "私に割り当てられました", + "filter-cid-all": "全てのカテゴリ", + "apply-filters": "フィルターを追加", + + "quick-links": "クイックリンク", + "flagged-user": "フラグを立てたユーザー", + "view-profile": "プロフィールを見る", + "start-new-chat": "新しいチャットを開始", + "go-to-target": "フラグのターゲットを表示", + + "user-view": "プロフィールを見る", + "user-edit": "プロフィールを編集", + + "notes": "ノートにフラグをつける", + "add-note": "ノートを追加", + "no-notes": "共有ノートはありません。", + + "history": "フラグ履歴", + "back": "フラグリストに戻る", + "no-history": "フラグ履歴がありません", + + "state-all": "全ての状態", + "state-open": "新規/開く", + "state-wip": "進行中の作業", + "state-resolved": "解決済み", + "state-rejected": "拒否済", + "no-assignee": "割り当てられていない", + "note-added": "ノートが追加されました", + + "modal-title": "不適切なコンテンツを報告する", + "modal-body": "レビューのために%1 %2 にフラグを付ける理由を指定してください。または必要に応じてクイックレポートボタンの1つを使用します。", + "modal-reason-spam": "スパム", + "modal-reason-offensive": "攻撃", + "modal-reason-custom": "このコンテンツを報告する理由...", + "modal-submit": "レポートを提出", + "modal-submit-success": "コンテンツはモデレーションにフラグ付けされています。" +} \ No newline at end of file diff --git a/public/language/ja/groups.json b/public/language/ja/groups.json index 275f3acd0f..53d1cc1cfb 100644 --- a/public/language/ja/groups.json +++ b/public/language/ja/groups.json @@ -27,7 +27,7 @@ "details.disableJoinRequests": "参加申請を無効にする", "details.grant": "寄贈/取り消す管理権限", "details.kick": "キック", - "details.kick_confirm": "Are you sure you want to remove this member from the group?", + "details.kick_confirm": "このメンバーをグループから削除", "details.owner_options": "グループの管理", "details.group_name": "グループ名", "details.member_count": "メンバー数", diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json index 938c217701..64678bf638 100644 --- a/public/language/ja/modules.json +++ b/public/language/ja/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3ヶ月", "chat.delete_message_confirm": "本当にこのメッセージを削除しますか?", "chat.add-users-to-room": "部屋にユーザーを追加", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "構成", "composer.show_preview": "プレビュー表示", "composer.hide_preview": "プレビュー非表示", diff --git a/public/language/ja/notifications.json b/public/language/ja/notifications.json index 8b461c3a83..bd93397a09 100644 --- a/public/language/ja/notifications.json +++ b/public/language/ja/notifications.json @@ -40,7 +40,7 @@ "user_started_following_you_multiple": "%1 と %2 または他のユーザーがあなたをフォローしました。", "new_register": "%1が登録リクエストを送りました。", "new_register_multiple": "%1の登録リクエストがレビュー待ちです。", - "flag_assigned_to_you": "Flag %1 has been assigned to you", + "flag_assigned_to_you": "フラグ %1はあなたに割当てられました", "email-confirmed": "Eメールが確認されました", "email-confirmed-message": "メールアドレス検証をして頂き、ありがとうございます。あなたのアカウントは完全にアクティブになりました。", "email-confirm-error-message": "あなたのEメールアドレス検証に問題があります。コードが無効か、期限切れです。", diff --git a/public/language/ja/user.json b/public/language/ja/user.json index 0f144ba5ef..4889823207 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "このユーザー名はすでに使用されています。いまのユーザー名は %1 です。", "password_same_as_username": "パスワードがユーザー名と同じですから、他のパスワードを使って下さい。", "password_same_as_email": "パスワードがメールアドレスと同じです。他のパスワードを使って下さい。", + "weak_password": "Weak password.", "upload_picture": "画像をアップロード", "upload_a_picture": "画像をアップロード", "remove_uploaded_picture": "アップした写真を取り消します", diff --git a/public/language/ko/admin/advanced/database.json b/public/language/ko/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/ko/admin/advanced/database.json +++ b/public/language/ko/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/ko/admin/manage/flags.json b/public/language/ko/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/ko/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/ko/admin/manage/groups.json b/public/language/ko/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/ko/admin/manage/groups.json +++ b/public/language/ko/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/ko/admin/settings/advanced.json b/public/language/ko/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/ko/admin/settings/advanced.json +++ b/public/language/ko/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/ko/admin/settings/post.json b/public/language/ko/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/ko/admin/settings/post.json +++ b/public/language/ko/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/ko/admin/settings/user.json b/public/language/ko/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/ko/admin/settings/user.json +++ b/public/language/ko/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/ko/email.json b/public/language/ko/email.json index 07a668917b..79259b2a40 100644 --- a/public/language/ko/email.json +++ b/public/language/ko/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "이 게시물 알림은 사용자의 구독 설정에 따라 전송되었습니다.", "test.text1": "이 시험용 메일은 NodeBB에 설정된 메일 송신자가 정상적으로 메일을 송신할 수 있는지 시험할 목적으로 발송되었습니다.", "unsub.cta": "설정을 변경하려면 여기를 클릭하세요.", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "감사합니다!" } \ No newline at end of file diff --git a/public/language/ko/error.json b/public/language/ko/error.json index 72110085cc..f67dae56bb 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -30,6 +30,7 @@ "password-too-long": "패스워드가 너무 깁니다.", "user-banned": "차단된 사용자입니다.", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "죄송합니다, 첫 번째 게시물은 %1 초 후에 작성할 수 있습니다.", "blacklisted-ip": "죄송하지만, 당신의 IP는 이 커뮤니티로부터 차단되었습니다. 만약 에러라는 생각이 드신다면 관리자에게 연락해주세요.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "대화 기능을 사용하지 않습니다.", "too-many-messages": "짧은 시간동안 너무 많은 메시지를 전송하였습니다. 잠시 후에 다시 시도하세요.", "invalid-chat-message": "올바르지 않은 메시지입니다.", - "chat-message-too-long": "메시지가 너무 깁니다.", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "편집 할 수 있는 권한이 없습니다.", "cant-remove-last-user": "마지막 사용자를 삭제할 수 없습니다.", "cant-delete-chat-message": "메세지를 지울 권한이 없습니다.", diff --git a/public/language/ko/flags.json b/public/language/ko/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/ko/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index 10420c4f75..e85fbc9763 100644 --- a/public/language/ko/modules.json +++ b/public/language/ko/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3개월", "chat.delete_message_confirm": "이 대화를 삭제하시겠습니까?", "chat.add-users-to-room": "유저 추가하기", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "작성", "composer.show_preview": "미리보기", "composer.hide_preview": "미리보기 숨김", diff --git a/public/language/ko/user.json b/public/language/ko/user.json index db32ec7755..f54cadc53e 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "새 사용자 이름이 이미 존재하여 %1로 저장되었습니다.", "password_same_as_username": "비밀번호가 사용자명과 동일합니다. 다른 비밀번호를 입력하세요.", "password_same_as_email": "비밀번호가 이메일 주소와 동일합니다. 다른 비밀번호를 입력하세요.", + "weak_password": "Weak password.", "upload_picture": "사진 업로드", "upload_a_picture": "사진 업로드", "remove_uploaded_picture": "등록된 사진을 삭제", diff --git a/public/language/lt/admin/advanced/database.json b/public/language/lt/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/lt/admin/advanced/database.json +++ b/public/language/lt/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/lt/admin/manage/flags.json b/public/language/lt/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/lt/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/lt/admin/manage/groups.json b/public/language/lt/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/lt/admin/manage/groups.json +++ b/public/language/lt/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/lt/admin/settings/advanced.json b/public/language/lt/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/lt/admin/settings/advanced.json +++ b/public/language/lt/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/lt/admin/settings/post.json b/public/language/lt/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/lt/admin/settings/post.json +++ b/public/language/lt/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/lt/admin/settings/user.json b/public/language/lt/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/lt/admin/settings/user.json +++ b/public/language/lt/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/lt/email.json b/public/language/lt/email.json index d4f24c89e5..699f8cacd1 100644 --- a/public/language/lt/email.json +++ b/public/language/lt/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Šios žinutės perspėjimas buvo išsiųstas į tavo prenumeratos nustatymus", "test.text1": "Ši žinutė yra bandomoji kad įsitikint, kad vartotojas teisingai nustatė nustatymus tavo NodeBB", "unsub.cta": "Spauskite čia norėdami pakeisti šiuos nustatymus", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Ačiū!" } \ No newline at end of file diff --git a/public/language/lt/error.json b/public/language/lt/error.json index fb9718f86e..aa16eedd88 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "Vartotojas užblokuotas", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Atsiprašome, jūs įpareigoti palaukti %1 sekunde(s) prieš rašant pirmą pranešimą", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "Išsiuntėte per daug pranešimų, kurį laiką prašome palaukti.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/lt/flags.json b/public/language/lt/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/lt/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json index dd0afcdac7..0e102bc984 100644 --- a/public/language/lt/modules.json +++ b/public/language/lt/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 mėnesiai", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Sukomponuoti", "composer.show_preview": "Rodyti pavyzdį", "composer.hide_preview": "Slėpti pavyzdį", diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 3e146a76fe..b02ecfbf0e 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Jūsų norimas vartotojo vardas jau užimtas, todėl mes jį šiek tiek pakeitėme. Dabar jūs esate žinomas kaip %1", "password_same_as_username": "Jūsų slaptažodis sutampa su Jūsų vartotojo vardu. Dėl saugumo, prašome naudoti kitą slaptažodį.", "password_same_as_email": "Jūsų slaptažodis sutampa su Jūsų el. pašto adresu. Dėl saugumo, prašome naudoti kitą slaptažodį.", + "weak_password": "Weak password.", "upload_picture": "Įkelti paveikslėlį", "upload_a_picture": "Įkelti paveikslėlį", "remove_uploaded_picture": "Ištrinti paveikslėlį", diff --git a/public/language/ms/admin/advanced/database.json b/public/language/ms/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/ms/admin/advanced/database.json +++ b/public/language/ms/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/ms/admin/manage/flags.json b/public/language/ms/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/ms/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/ms/admin/manage/groups.json b/public/language/ms/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/ms/admin/manage/groups.json +++ b/public/language/ms/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/ms/admin/settings/advanced.json b/public/language/ms/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/ms/admin/settings/advanced.json +++ b/public/language/ms/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/ms/admin/settings/post.json b/public/language/ms/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/ms/admin/settings/post.json +++ b/public/language/ms/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/ms/admin/settings/user.json b/public/language/ms/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/ms/admin/settings/user.json +++ b/public/language/ms/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/ms/email.json b/public/language/ms/email.json index eb5affbabd..804155fd84 100644 --- a/public/language/ms/email.json +++ b/public/language/ms/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Kiriman pemberitahuan ini dihantar berdasarkan tetapan langganan anda.", "test.text1": "Ini adalah percubaan email untuk mengesahkan emailer ditetap dengan betul di NodeBB.", "unsub.cta": "Klik sini untuk mengubah tetapan itu", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Terima Kasih!" } \ No newline at end of file diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 25582f7d3e..34f838b4b4 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -30,6 +30,7 @@ "password-too-long": "Kata laluan terlalu panjang", "user-banned": "Pengguna diharamkan", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Maaf, anda dikehendaki menunggu %1 saat() sebelum membuat kiriman pertama anda", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Sistem borak tidak diaktifkan", "too-many-messages": "Anda menghantar terlalu banyak pesanan, sila tunggu seketika.", "invalid-chat-message": "Mesej borak tidak sah", - "chat-message-too-long": "Mesej borak terlalu panjang", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Anda tidak dibenarkan menyunting mesej ini", "cant-remove-last-user": "Anda tidak boleh membuang pengguna akhir", "cant-delete-chat-message": "Anda tidak dibenarkan memadamkan mesej ini", diff --git a/public/language/ms/flags.json b/public/language/ms/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/ms/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json index 4e3f9bf58b..67c96b75a2 100644 --- a/public/language/ms/modules.json +++ b/public/language/ms/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Bulan", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Tulis", "composer.show_preview": "Pra-lihat", "composer.hide_preview": "Sorok pra-lihat", diff --git a/public/language/ms/user.json b/public/language/ms/user.json index a07b860972..bc1074bc5e 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Nama pengguna yang anda minta telah digunakan oleh orang lain, jadi kami telah mengubahsuaikannya sedikit. Anda kini dikenali sebagai %1", "password_same_as_username": "Kata laluan anda adalah sama seperti nama pengguna, sila pilih kata laluan yang lain", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Muatnaik gambar", "upload_a_picture": "Muatnaik sekeping gambar", "remove_uploaded_picture": "Buang Gambar Yang Dimuatnaik", diff --git a/public/language/nb/admin/advanced/database.json b/public/language/nb/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/nb/admin/advanced/database.json +++ b/public/language/nb/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/nb/admin/manage/flags.json b/public/language/nb/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/nb/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/nb/admin/manage/groups.json b/public/language/nb/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/nb/admin/manage/groups.json +++ b/public/language/nb/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/nb/admin/settings/advanced.json b/public/language/nb/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/nb/admin/settings/advanced.json +++ b/public/language/nb/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/nb/admin/settings/post.json b/public/language/nb/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/nb/admin/settings/post.json +++ b/public/language/nb/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/nb/admin/settings/user.json b/public/language/nb/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/nb/admin/settings/user.json +++ b/public/language/nb/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/nb/email.json b/public/language/nb/email.json index 6377d85fbf..916aa5880b 100644 --- a/public/language/nb/email.json +++ b/public/language/nb/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Dette innleggsvarselet ble sendt til deg basert på dine innstillinger for abonnering.", "test.text1": "Dette er en test e-post for å verifisere at e-postsystemet i NodeBB fungerer som det skal.", "unsub.cta": "Klikk her for å endre disse innstillingene", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Takk!" } \ No newline at end of file diff --git a/public/language/nb/error.json b/public/language/nb/error.json index 5e25fa5557..8c63511a83 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "Bruker utestengt", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Beklager, du må vente %1 sekund(er) før du oppretter ditt første innlegg", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "Du har sendt for mange meldinger, vennligst vent en stund.", "invalid-chat-message": "Ugyldig samtalemelding", - "chat-message-too-long": "Samtalemeldingen er for lang", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/nb/flags.json b/public/language/nb/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/nb/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json index 1f6bcd2c44..72c205d3b1 100644 --- a/public/language/nb/modules.json +++ b/public/language/nb/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 måneder", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Komponer", "composer.show_preview": "Vis forhåndsvisning", "composer.hide_preview": "Skjul forhåndsvisning", diff --git a/public/language/nb/user.json b/public/language/nb/user.json index af209863a7..5a71bd8a5e 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Brukernavnet du ønsket er opptatt, så vi har endret ditt litt. Du er nå kjent som %1", "password_same_as_username": "Ditt passord er det samme som ditt brukernavn, vennligst velg et annet passord.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Last opp bilde", "upload_a_picture": "Last opp et bilde", "remove_uploaded_picture": "Fjern Opplastet Bilde", diff --git a/public/language/nl/admin/advanced/database.json b/public/language/nl/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/nl/admin/advanced/database.json +++ b/public/language/nl/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/nl/admin/manage/flags.json b/public/language/nl/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/nl/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/nl/admin/manage/groups.json b/public/language/nl/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/nl/admin/manage/groups.json +++ b/public/language/nl/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/nl/admin/settings/advanced.json b/public/language/nl/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/nl/admin/settings/advanced.json +++ b/public/language/nl/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/nl/admin/settings/post.json b/public/language/nl/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/nl/admin/settings/post.json +++ b/public/language/nl/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/nl/admin/settings/user.json b/public/language/nl/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/nl/admin/settings/user.json +++ b/public/language/nl/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/nl/email.json b/public/language/nl/email.json index c010b77769..d94f164e9e 100644 --- a/public/language/nl/email.json +++ b/public/language/nl/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Deze notificatie is door ons verzonden vanwege gebruikersinstellingen voor abonnementen en berichten.", "test.text1": "Dit is een testbericht om te verifiëren dat NodeBB de e-mailberichtservice correct heeft opgezet.", "unsub.cta": "Klik hier om deze instellingen te wijzigen", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Bedankt!" } \ No newline at end of file diff --git a/public/language/nl/error.json b/public/language/nl/error.json index 850ebe7db9..588d2d3a1b 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -30,6 +30,7 @@ "password-too-long": "Wachtwoord is te lang", "user-banned": "Gebruiker verbannen", "user-banned-reason": "Sorry, dit account is verbannen (Reden: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Helaas, het is een vereiste om %1 seconde(n) te wachten voordat het eerste bericht geplaatst kan worden.", "blacklisted-ip": "Sorry, uw IP-adres is verbannen uit deze community. Als u meent dat dit onterecht is, neem dan contact op met een beheerder.", "ban-expiry-missing": "Geef een einddatum op voor deze ban.", @@ -104,7 +105,7 @@ "chat-disabled": "Chat systeem uitgeschakeld", "too-many-messages": "Je hebt in korte tijd veel berichten verstuurd, als je even wacht mag je weer berichten sturen.", "invalid-chat-message": "Ongeldig bericht", - "chat-message-too-long": "Het chatbericht is te lang", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Het is niet toegestaan om dit bericht aan te passen", "cant-remove-last-user": "Je kan de laatste gebruiker niet verwijderen", "cant-delete-chat-message": "Het is niet toegestaan om dit bericht te verwijderen", diff --git a/public/language/nl/flags.json b/public/language/nl/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/nl/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json index 59d0396464..aac1dd231b 100644 --- a/public/language/nl/modules.json +++ b/public/language/nl/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 maanden", "chat.delete_message_confirm": "Weet je zeker dat je dit bericht wilt verwijderen?", "chat.add-users-to-room": "Voeg gebruikers toe aan deze chat room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Samenstellen", "composer.show_preview": "Voorbeeldweergave", "composer.hide_preview": "Verberg voorbeeld", diff --git a/public/language/nl/user.json b/public/language/nl/user.json index 9c753904f6..12b92dc7dd 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -60,6 +60,7 @@ "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": "Je wachtwoord is hetzelfde als je email, kies alsjeblieft een ander wachtwoord.", + "weak_password": "Weak password.", "upload_picture": "Upload afbeelding", "upload_a_picture": "Upload een afbeelding", "remove_uploaded_picture": "Verwijder gëuploade foto", diff --git a/public/language/pl/admin/advanced/database.json b/public/language/pl/admin/advanced/database.json index c4e29ca7c1..dd365e5b0b 100644 --- a/public/language/pl/admin/advanced/database.json +++ b/public/language/pl/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime w sekundach", "uptime-days": "Uptime w dniach", diff --git a/public/language/pl/admin/manage/flags.json b/public/language/pl/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/pl/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/pl/admin/manage/groups.json b/public/language/pl/admin/manage/groups.json index 3de74fad4a..b5438179bd 100644 --- a/public/language/pl/admin/manage/groups.json +++ b/public/language/pl/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Nazwa Grupy", "description": "Opis Grupy", + "member-count": "Member Count", "system": "System Grup", "edit": "Edytuj", "search-placeholder": "Szukaj", diff --git a/public/language/pl/admin/settings/advanced.json b/public/language/pl/admin/settings/advanced.json index d931155ad3..6269f725a1 100644 --- a/public/language/pl/admin/settings/advanced.json +++ b/public/language/pl/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/pl/admin/settings/post.json b/public/language/pl/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/pl/admin/settings/post.json +++ b/public/language/pl/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/pl/admin/settings/user.json b/public/language/pl/admin/settings/user.json index 94df909293..ca27eca846 100644 --- a/public/language/pl/admin/settings/user.json +++ b/public/language/pl/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/pl/email.json b/public/language/pl/email.json index cf511906ed..65434dd2d4 100644 --- a/public/language/pl/email.json +++ b/public/language/pl/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "To powiadomienie o poście zostało Ci wysłane zgodnie z ustawieniami Twojego konta.", "test.text1": "To jest e-mail testowy, aby sprawdzić, czy poprawnie skonfigurowałeś e-mailer w swoim NodeBB.", "unsub.cta": "Kliknij tutaj, by zmienić te ustawienia", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Dziękujemy!" } \ No newline at end of file diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 5da5652888..9e1bcf4ae4 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -30,6 +30,7 @@ "password-too-long": "Hasło jest za długie", "user-banned": "Użytkownik zbanowany", "user-banned-reason": "Twoje konto zostało zablokowane (Powód: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Przepraszamy, musisz odczekać %1 sekund(y) przed utworzeniem pierwszego posta", "blacklisted-ip": "Twój adres IP został zablokowany na tej społeczności. Jeśli uważasz to za błąd, zgłoś to administratorowi", "ban-expiry-missing": "Wprowadź datę końca blokady", @@ -104,7 +105,7 @@ "chat-disabled": "System rozmów jest wyłączony", "too-many-messages": "Wysłałeś zbyt wiele wiadomości, prosimy chwilę poczekać.", "invalid-chat-message": "Nieprawidłowa wiadomość", - "chat-message-too-long": "Wiadomość jest zbyt długa", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Nie jesteś upoważniony do edycji tej wiadomości", "cant-remove-last-user": "You can't remove the last user", "cant-delete-chat-message": "Nie jesteś upoważniony do usunięcia tej wiadomości", diff --git a/public/language/pl/flags.json b/public/language/pl/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/pl/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index ac5af73422..1afc9a68da 100644 --- a/public/language/pl/modules.json +++ b/public/language/pl/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 miesiące", "chat.delete_message_confirm": "Jesteś pewny, że chcesz usunąć tą wiadomość?", "chat.add-users-to-room": "Dodaj użytkownika do pokoju czatu", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Twórz", "composer.show_preview": "Pokaż Podgląd", "composer.hide_preview": "Ukryj Podgląd", diff --git a/public/language/pl/user.json b/public/language/pl/user.json index 75547f5c27..795470daa5 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Wybrany login jest już zajęty, więc zmieniliśmy go trochę. Proponujemy %1", "password_same_as_username": "Twoje hasło jest takie samo jak nazwa użytkownika, prosimy wybrać inne hasło.", "password_same_as_email": "Twoje hasło jest takie samo jak adres e-mail, prosimy wybrać inne hasło.", + "weak_password": "Weak password.", "upload_picture": "Prześlij zdjęcie", "upload_a_picture": "Prześlij zdjęcie", "remove_uploaded_picture": "Usuń Przesłane Zdjęcie", diff --git a/public/language/pt-BR/admin/advanced/database.json b/public/language/pt-BR/admin/advanced/database.json index e887b1069a..a7299086b2 100644 --- a/public/language/pt-BR/admin/advanced/database.json +++ b/public/language/pt-BR/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Tempo Rodando em Segundos", "uptime-days": "Tempo Rodando em Dias", diff --git a/public/language/pt-BR/admin/development/info.json b/public/language/pt-BR/admin/development/info.json index fe35d78a37..6b16f9a46a 100644 --- a/public/language/pt-BR/admin/development/info.json +++ b/public/language/pt-BR/admin/development/info.json @@ -1,5 +1,5 @@ { - "you-are-on": "Info - Você está em %1:%2", + "you-are-on": "Informação - Você está em %1:%2", "host": "host", "pid": "pid", "nodejs": "nodejs", diff --git a/public/language/pt-BR/admin/manage/flags.json b/public/language/pt-BR/admin/manage/flags.json deleted file mode 100644 index 528dcc47d0..0000000000 --- a/public/language/pt-BR/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Sinalizações diárias", - "by-user": "Sinalizações pelo usuário", - "by-user-search": "Procurar posts sinalizados por nome de usuário", - "category": "Categoria", - "sort-by": "Escolher Por", - "sort-by.most-flags": "Mais Sinalizações", - "sort-by.most-recent": "Mais Recente", - "search": "Pesquisar", - "dismiss-all": "Dispersar Tudo", - "none-flagged": "Sem posts sinalizados!", - "posted-in": "Postado em %1", - "read-more": "Leia Mais", - "flagged-x-times": "Este post foi sinalizado %1 vez(es):", - "dismiss": "Desfazer esta Sinalização", - "delete-post": "Excluir o Post", - - "alerts.confirm-delete-post": "Você realmente quer excluir este post?" -} \ No newline at end of file diff --git a/public/language/pt-BR/admin/manage/groups.json b/public/language/pt-BR/admin/manage/groups.json index c1d4a34f88..6ec69b4ac5 100644 --- a/public/language/pt-BR/admin/manage/groups.json +++ b/public/language/pt-BR/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Nome do Grupo", "description": "Descrição do Grupo", + "member-count": "Member Count", "system": "Grupo do Sistema", "edit": "Editar", "search-placeholder": "Procurar", diff --git a/public/language/pt-BR/admin/manage/tags.json b/public/language/pt-BR/admin/manage/tags.json index d163e643fd..4bf5b27a3e 100644 --- a/public/language/pt-BR/admin/manage/tags.json +++ b/public/language/pt-BR/admin/manage/tags.json @@ -1,5 +1,5 @@ { - "none": "O seu fórum ainda não tem quaisquer tópicos com tags.", + "none": "O seu fórum ainda não tem tópicos com tags.", "bg-color": "Cor de Fundo", "text-color": "Cor do Text", "create-modify": "Criar & Modificar Tags", diff --git a/public/language/pt-BR/admin/settings/advanced.json b/public/language/pt-BR/admin/settings/advanced.json index 8d37677031..87f5545286 100644 --- a/public/language/pt-BR/admin/settings/advanced.json +++ b/public/language/pt-BR/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Defina ALLOW-FROM para Colocar o NodeBB em um iFrame", "headers.powered-by": "Personalizar o cabeçalho de \"Powered By\" enviado pelo NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "Para impedir acesso à todos os sites, deixe em branco ou define como null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Administração de Tráfego", diff --git a/public/language/pt-BR/admin/settings/guest.json b/public/language/pt-BR/admin/settings/guest.json index 9f3c4d57a9..2c4d0d4565 100644 --- a/public/language/pt-BR/admin/settings/guest.json +++ b/public/language/pt-BR/admin/settings/guest.json @@ -1,5 +1,5 @@ { - "handles": "Handles para Visitantes", + "handles": "Apelidos para Visitantes", "handles.enabled": "Permitir handles de visitantes", "handles.enabled-help": "Esta opção mostra um novo campo que permite visitantes de escolher um nome para associar à cada post que eles fizerem. Se desabilitado, eles serão simplesmente chamados de \"Visitante\".", "privileges": "Privilégios de Visitantes", diff --git a/public/language/pt-BR/admin/settings/post.json b/public/language/pt-BR/admin/settings/post.json index 81a18b002d..902000e386 100644 --- a/public/language/pt-BR/admin/settings/post.json +++ b/public/language/pt-BR/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Configurações de Não-Lidos", "unread.cutoff": "Data de corte de não-lidos", "unread.min-track-last": "Mínimo de posts no tópico antes de rastrear o último lido", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Configurações de Assinatura", "signature.disable": "Desabilitar assinaturas", "signature.no-links": "Desabilitar links em assinaturas", diff --git a/public/language/pt-BR/admin/settings/reputation.json b/public/language/pt-BR/admin/settings/reputation.json index c81dd2364f..86d774ff99 100644 --- a/public/language/pt-BR/admin/settings/reputation.json +++ b/public/language/pt-BR/admin/settings/reputation.json @@ -4,6 +4,6 @@ "disable-down-voting": "Desabilitar Baixo Votar", "votes-are-public": "All Votes Are Public", "thresholds": "Limiares de Atividade", - "min-rep-downvote": "Reputação mínima para baixovotar posts", + "min-rep-downvote": "Reputação mínima para votar negativamente em posts", "min-rep-flag": "Reputação mínima para sinalizar posts" } \ No newline at end of file diff --git a/public/language/pt-BR/admin/settings/user.json b/public/language/pt-BR/admin/settings/user.json index afeb270c5b..9ccdd6baa5 100644 --- a/public/language/pt-BR/admin/settings/user.json +++ b/public/language/pt-BR/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Tamanho Mínimo do Nome de Usuário", "max-username-length": "Tamanho Máximo do Nome de Usuário", "min-password-length": "Tamanho Mínimo da Senha", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Tamanho Máximo do Sobre Mim", "terms-of-use": "Termos de Uso do Fórum (Deixar em branco para desabilitar)", "user-search": "Pesquisa de Usuário", diff --git a/public/language/pt-BR/category.json b/public/language/pt-BR/category.json index c064d31ade..2f925a0e37 100644 --- a/public/language/pt-BR/category.json +++ b/public/language/pt-BR/category.json @@ -15,6 +15,6 @@ "watching.description": "Mostrar tópicos em não-lido", "ignoring.description": "Não mostrar tópicos em não-lido", "watch.message": "Agora você está seguindo as atualizações desta categoria e de todas as subcategorias", - "ignore.message": "Agora você está ignorando as atualizações desta categoria e de todas as subcategorias", + "ignore.message": "Agora você está ignorando as atualizações desta categoria e de todas as suas subcategorias", "watched-categories": "Categorias acompanhadas" } \ No newline at end of file diff --git a/public/language/pt-BR/email.json b/public/language/pt-BR/email.json index 64ef3928a9..766152ee05 100644 --- a/public/language/pt-BR/email.json +++ b/public/language/pt-BR/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Esta notificação de postagem foi enviada para você devido as suas configurações de assinatura.", "test.text1": "Este é um e-mail de teste, para verificar que o enviador de emails está corretamente configurado no seu NodeBB.", "unsub.cta": "Clique aqui para alterar estas configurações", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Obrigado!" } \ No newline at end of file diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json index 14e3448ae3..ba2053e547 100644 --- a/public/language/pt-BR/error.json +++ b/public/language/pt-BR/error.json @@ -30,6 +30,7 @@ "password-too-long": "A senha é muito grande", "user-banned": "Usuário banido", "user-banned-reason": "Desculpa, esta conta foi banida (Motivo: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Desculpe, é necessário que você aguarde %1 segundo(s) antes de fazer o seu primeiro post.", "blacklisted-ip": "Desculpe, o seu endereço IP foi banido desta comunidade. Se você acha que isso é um engano, por favor contate um administrador.", "ban-expiry-missing": "Por favor forneça uma data para o fim deste banimento", @@ -104,7 +105,7 @@ "chat-disabled": "O sistema de chat foi desabilitado", "too-many-messages": "Você enviou muitas mensagens, por favor aguarde um momento.", "invalid-chat-message": "Mensagem de chat inválida", - "chat-message-too-long": "A mensagem de chat é muito longa", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Você não tem permissão para editar esta mensagem", "cant-remove-last-user": "Você não pode excluir o último usuário", "cant-delete-chat-message": "Você não possui permissão para deletar esta mensagem", diff --git a/public/language/pt-BR/flags.json b/public/language/pt-BR/flags.json new file mode 100644 index 0000000000..802b042fa3 --- /dev/null +++ b/public/language/pt-BR/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Estado", + "reporter": "Reportado por", + "reported-at": "Reportado Em", + "description": "Descrição", + "no-flags": "Ihuul! Nenhuma sinalização encontrada.", + "assignee": "Cessionário", + "update": "Atualizar", + "updated": "Atualizado", + "target-purged": "O conteúdo ao qual essa sinalização se referia foi removido e não está mais disponível.", + + "quick-filters": "Filtros Rápidos", + "filter-active": "Há um ou mais filtros ativos nesta lista de sinalizações", + "filter-reset": "Remover Filtros", + "filters": "Opções de Filtro", + "filter-reporterId": "UID do Reportador", + "filter-targetUid": "UID Sinalizado", + "filter-type": "Tipo de Sinalização", + "filter-type-all": "Todo o Conteúdo", + "filter-type-post": "Post", + "filter-state": "Estado", + "filter-assignee": "UID do Cessionário", + "filter-cid": "Categoria", + "filter-quick-mine": "Procurado à mim", + "filter-cid-all": "Todas as categorias", + "apply-filters": "Aplicar Filtros", + + "quick-links": "Links Rápidos", + "flagged-user": "Usuário Sinalizado", + "view-profile": "Ver Perfil", + "start-new-chat": "Iniciar Novo Chat", + "go-to-target": "Ver Sinalizado", + + "user-view": "Ver Perfil", + "user-edit": "Editar Perfil", + + "notes": "Notas da Sinalização", + "add-note": "Adicionar Nota", + "no-notes": "Nenhuma nota compartilhada.", + + "history": "Histórico de Sinalizações", + "back": "Voltar à Lista de Sinaliações", + "no-history": "Sem histórico de sinalizações.", + + "state-all": "Todos os estados", + "state-open": "Novo/Aberto", + "state-wip": "Trabalho em Progresso", + "state-resolved": "Resolvido", + "state-rejected": "Rejeitado", + "no-assignee": "Não Procurado", + "note-added": "Nota Adicionada", + + "modal-title": "Reportar Conteúdo Inadequado", + "modal-body": "Por favor especifique sua razão para sinalizar %1 %2 para revisão. Alternativamente, use um dos botões de reporte rápido se for aplicável.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Ofensivo", + "modal-reason-custom": "Motivo para reportar este conteúdo...", + "modal-submit": "Enviar Reportagem", + "modal-submit-success": "O conteúdo foi sinalizado para moderação." +} \ No newline at end of file diff --git a/public/language/pt-BR/global.json b/public/language/pt-BR/global.json index c6220177a4..914a11e049 100644 --- a/public/language/pt-BR/global.json +++ b/public/language/pt-BR/global.json @@ -53,10 +53,10 @@ "topics": "Tópicos", "posts": "Posts", "best": "Melhor", - "upvoters": "Cimavotadores", - "upvoted": "Votado positivamente", - "downvoters": "Baixovotadores", - "downvoted": "Votado negativamente", + "upvoters": "Votos positivos", + "upvoted": "Votou positivamente", + "downvoters": "Votos negativos", + "downvoted": "Votou negativamente", "views": "Visualizações", "reputation": "Reputação", "read_more": "ler mais", diff --git a/public/language/pt-BR/modules.json b/public/language/pt-BR/modules.json index ff7568f89a..0a2425ce03 100644 --- a/public/language/pt-BR/modules.json +++ b/public/language/pt-BR/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Meses", "chat.delete_message_confirm": "Tem certeza que deseja excluir esta mensagem?", "chat.add-users-to-room": "Adicionar usuários à sala", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compor", "composer.show_preview": "Exibir Pré-visualização", "composer.hide_preview": "Esconder Pré-visualização", diff --git a/public/language/pt-BR/user.json b/public/language/pt-BR/user.json index 28a5741924..e242fc41b4 100644 --- a/public/language/pt-BR/user.json +++ b/public/language/pt-BR/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "O nome de usuário que você escolheu já existia, então nós o alteramos um pouquinho. Agora você é conhecido como %1", "password_same_as_username": "A sua senha é igual ao seu nome de usuário, por favor escolha outra senha.", "password_same_as_email": "Tua senha é a mesma que o teu email, por favor escolha outra senha.", + "weak_password": "Weak password.", "upload_picture": "Carregar Foto", "upload_a_picture": "Carregue uma Foto", "remove_uploaded_picture": "Remover Foto Enviada", diff --git a/public/language/pt-PT/admin/admin.json b/public/language/pt-PT/admin/admin.json index 9c01f56006..349b832a5c 100644 --- a/public/language/pt-PT/admin/admin.json +++ b/public/language/pt-PT/admin/admin.json @@ -3,5 +3,5 @@ "alert.confirm-restart": "Are you sure you wish to restart NodeBB?", "acp-title": "%1 | NodeBB Admin Control Panel", - "settings-header-contents": "Contents" + "settings-header-contents": "Conteúdo" } \ No newline at end of file diff --git a/public/language/pt-PT/admin/advanced/database.json b/public/language/pt-PT/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/pt-PT/admin/advanced/database.json +++ b/public/language/pt-PT/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/pt-PT/admin/manage/flags.json b/public/language/pt-PT/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/pt-PT/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/pt-PT/admin/manage/groups.json b/public/language/pt-PT/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/pt-PT/admin/manage/groups.json +++ b/public/language/pt-PT/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/pt-PT/admin/settings/advanced.json b/public/language/pt-PT/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/pt-PT/admin/settings/advanced.json +++ b/public/language/pt-PT/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/pt-PT/admin/settings/post.json b/public/language/pt-PT/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/pt-PT/admin/settings/post.json +++ b/public/language/pt-PT/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/pt-PT/admin/settings/user.json b/public/language/pt-PT/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/pt-PT/admin/settings/user.json +++ b/public/language/pt-PT/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/pt-PT/email.json b/public/language/pt-PT/email.json index 8f347ea8b4..deddc31cb2 100644 --- a/public/language/pt-PT/email.json +++ b/public/language/pt-PT/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Esta notificação foi envidada devido às tuas definições de subscrição.", "test.text1": "Este é um e-mail de teste para verificar que o emailer está configurado corretamente para o teu NodeBB.", "unsub.cta": "Clica aqui para alterares essas definições", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Obrigado!" } \ No newline at end of file diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json index d8ee847d2f..d87e005288 100644 --- a/public/language/pt-PT/error.json +++ b/public/language/pt-PT/error.json @@ -30,6 +30,7 @@ "password-too-long": "Palavra-passe muito longa", "user-banned": "Utilizador banido", "user-banned-reason": "Desculpa, esta conta foi banida (Razão: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Desculpa, é necessário que esperes %1 segundo(s) antes de fazeres a tua primeira publicação", "blacklisted-ip": "Desculpa, o teu endereço IP foi banido desta comunidade. Se sentes que isto é um erro, por favor contacta o administrador.", "ban-expiry-missing": "Por favor providencia uma data para o fim deste banimento", @@ -104,7 +105,7 @@ "chat-disabled": "Sistema de conversas desativado", "too-many-messages": "Enviaste demasiadas mensagens, por favor espera um pouco.", "invalid-chat-message": "Mensagem de chat inválida", - "chat-message-too-long": "Mensagem de chat demasiado longa", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Não tens permissão para editar esta mensagem", "cant-remove-last-user": "Não podes remover o último utilizador", "cant-delete-chat-message": "Não tens permissão para eliminar esta mensagem", diff --git a/public/language/pt-PT/flags.json b/public/language/pt-PT/flags.json new file mode 100644 index 0000000000..8d1d58cbf4 --- /dev/null +++ b/public/language/pt-PT/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Descrição", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Atualizar", + "updated": "Atualizado", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Filtros Rápidos", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remover Filtros", + "filters": "Opções dos Filtros", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "Todo o Conteúdo", + "filter-type-post": "Publicação", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Categoria", + "filter-quick-mine": "Atribuído a mim", + "filter-cid-all": "Todas as categorias", + "apply-filters": "Aplicar Filtros", + + "quick-links": "Links Rápidos", + "flagged-user": "Utilizador Sinalizado", + "view-profile": "Ver Perfil", + "start-new-chat": "Iniciar Nova Conversa", + "go-to-target": "Ver Alvo da Sinalização", + + "user-view": "Ver Perfil", + "user-edit": "Editar Perfil", + + "notes": "Sinalizar Notas", + "add-note": "Adicionar Nota", + "no-notes": "Não existem notas partilhadas.", + + "history": "Histórico de Sinalizações", + "back": "Voltar para a Lista de Sinalizações", + "no-history": "Não existe histórico de sinalizações.", + + "state-all": "Todos os estados", + "state-open": "Novo/Abrir", + "state-wip": "Trabalho em Progresso", + "state-resolved": "Resolvido", + "state-rejected": "Rejeitado", + "no-assignee": "Não Atribuído", + "note-added": "Nota Adicionada.", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Ofensivo", + "modal-reason-custom": "Motivo para reportar este conteúdo...", + "modal-submit": "Submeter Relatório", + "modal-submit-success": "Conteúdo sinalizado para moderação." +} \ No newline at end of file diff --git a/public/language/pt-PT/modules.json b/public/language/pt-PT/modules.json index 6164606984..5c7ffcaca4 100644 --- a/public/language/pt-PT/modules.json +++ b/public/language/pt-PT/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 meses", "chat.delete_message_confirm": "Tens a certeza que desejas apagar esta mensagem?", "chat.add-users-to-room": "Adicionar utilizadores à sala", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compor", "composer.show_preview": "Mostrar pré-visualização", "composer.hide_preview": "Ocultar pré-visualização", diff --git a/public/language/pt-PT/user.json b/public/language/pt-PT/user.json index 0f686ef666..6e5b6981b3 100644 --- a/public/language/pt-PT/user.json +++ b/public/language/pt-PT/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "O nome de utilizador que escolheste já está em utilização por isso alteramo-lo ligeiramente. És agora conhecido como %1", "password_same_as_username": "A tua palavra-passe é igual ao teu nome de utilizador. Por favor, escolhe outra palavra-passe.", "password_same_as_email": "A tua palavra-passe é a mesma que o teu e-mail. Por favor, escolhe outra palavra-passe.", + "weak_password": "Weak password.", "upload_picture": "Carregar imagem", "upload_a_picture": "Carregar uma imagem", "remove_uploaded_picture": "Remover imagem carregada", diff --git a/public/language/ro/admin/advanced/database.json b/public/language/ro/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/ro/admin/advanced/database.json +++ b/public/language/ro/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/ro/admin/manage/flags.json b/public/language/ro/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/ro/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/ro/admin/manage/groups.json b/public/language/ro/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/ro/admin/manage/groups.json +++ b/public/language/ro/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/ro/admin/settings/advanced.json b/public/language/ro/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/ro/admin/settings/advanced.json +++ b/public/language/ro/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/ro/admin/settings/post.json b/public/language/ro/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/ro/admin/settings/post.json +++ b/public/language/ro/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/ro/admin/settings/user.json b/public/language/ro/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/ro/admin/settings/user.json +++ b/public/language/ro/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/ro/email.json b/public/language/ro/email.json index e07033fd97..1fa8e4f2a8 100644 --- a/public/language/ro/email.json +++ b/public/language/ro/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "Acesta este un email de test pentru a verica dacă mailul este setat corect pentru NodeBB-ul tău.", "unsub.cta": "Apasă aici pentru a modifica acele setări", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Mulțumesc!" } \ No newline at end of file diff --git a/public/language/ro/error.json b/public/language/ro/error.json index 71c3b5cc7e..82112f70c0 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -30,6 +30,7 @@ "password-too-long": "Parola prea lunga.", "user-banned": "Utilizator banat", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Imi pare rau dar trebuie sa astepti %1 secunda(e) pentru a posta prima oara.", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/ro/flags.json b/public/language/ro/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/ro/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json index 47ab86acd9..c1a2cb2b4f 100644 --- a/public/language/ro/modules.json +++ b/public/language/ro/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Luni", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Scrie", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/ro/user.json b/public/language/ro/user.json index ccb1d8ebf7..205c7bfdbf 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Numele de utilizator pe care l-ai cerut este deja luat, așa că l-am modificat puțin. Acum ești cunoscut ca %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Uploadează poză", "upload_a_picture": "Uploadează o poză", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/ru/admin/advanced/cache.json b/public/language/ru/admin/advanced/cache.json index c0487f399b..70e88bd261 100644 --- a/public/language/ru/admin/advanced/cache.json +++ b/public/language/ru/admin/advanced/cache.json @@ -1,10 +1,10 @@ { - "post-cache": "Кэш записи", - "posts-in-cache": "Записей в кэше", - "average-post-size": "Средний размер записи", - "length-to-max": "Длина / Максимальная", + "post-cache": "Кэш сообщений", + "posts-in-cache": "Закешировано сообщений", + "average-post-size": "Средний размер сообщения", + "length-to-max": "Размер / Максимум", "percent-full": "%1% Full", - "post-cache-size": "Размер записи в кэше", + "post-cache-size": "Размер кэша сообщений", "items-in-cache": "Items in Cache", "control-panel": "Панель управления", "update-settings": "Обновить настройки кэша" diff --git a/public/language/ru/admin/advanced/database.json b/public/language/ru/admin/advanced/database.json index f7db6220ee..50e8aae0aa 100644 --- a/public/language/ru/admin/advanced/database.json +++ b/public/language/ru/admin/advanced/database.json @@ -1,33 +1,34 @@ { - "x-b": "%1 b", - "x-mb": "%1 mb", - "uptime-seconds": "Uptime in Seconds", - "uptime-days": "Uptime in Days", + "x-b": "%1 байт", + "x-mb": "%1 мегабайт", + "x-gb": "%1 gb", + "uptime-seconds": "Время работы в секундах", + "uptime-days": "Время работы в днях", "mongo": "Mongo", - "mongo.version": "MongoDB Version", + "mongo.version": "Версия MongoDB", "mongo.storage-engine": "Storage Engine", - "mongo.collections": "Collections", - "mongo.objects": "Objects", - "mongo.avg-object-size": "Avg. Object Size", - "mongo.data-size": "Data Size", - "mongo.storage-size": "Storage Size", + "mongo.collections": "Коллекции", + "mongo.objects": "Документы", + "mongo.avg-object-size": "Средний размер документа", + "mongo.data-size": "Размер данных", + "mongo.storage-size": "Размер хранилища", "mongo.index-size": "Index Size", - "mongo.file-size": "File Size", + "mongo.file-size": "Размер файла", "mongo.resident-memory": "Resident Memory", - "mongo.virtual-memory": "Virtual Memory", + "mongo.virtual-memory": "Виртуальная память", "mongo.mapped-memory": "Mapped Memory", - "mongo.raw-info": "MongoDB Raw Info", + "mongo.raw-info": "Сырые данные о MongoDB", "redis": "Redis", - "redis.version": "Redis Version", - "redis.connected-clients": "Connected Clients", + "redis.version": "Версия Redis", + "redis.connected-clients": "Подключенные клиенты", "redis.connected-slaves": "Connected Slaves", - "redis.blocked-clients": "Blocked Clients", - "redis.used-memory": "Used Memory", + "redis.blocked-clients": "Заблокированные клиенты", + "redis.used-memory": "Используемая Память", "redis.memory-frag-ratio": "Memory Fragmentation Ratio", - "redis.total-connections-recieved": "Total Connections Received", - "redis.total-commands-processed": "Total Commands Processed", + "redis.total-connections-recieved": "Общее число подключений получено", + "redis.total-commands-processed": "Команд обработано в общем", "redis.iops": "Instantaneous Ops. Per Second", "redis.keyspace-hits": "Keyspace Hits", "redis.keyspace-misses": "Keyspace Misses", diff --git a/public/language/ru/admin/advanced/errors.json b/public/language/ru/admin/advanced/errors.json index 546f0f1508..b3185f3cdb 100644 --- a/public/language/ru/admin/advanced/errors.json +++ b/public/language/ru/admin/advanced/errors.json @@ -1,14 +1,14 @@ { - "figure-x": "Figure %1", - "error-events-per-day": "%1 events per day", - "error.404": "404 Not Found", - "error.503": "503 Service Unavailable", - "manage-error-log": "Manage Error Log", + "figure-x": "Рисунок %1", + "error-events-per-day": "событий %1 в день", + "error.404": "404 Не найдено", + "error.503": "503 Сервис недоступен", + "manage-error-log": "Управление журналами ошибок", "export-error-log": "Export Error Log (CSV)", "clear-error-log": "Clear Error Log", - "route": "Route", - "count": "Count", - "no-routes-not-found": "Hooray! No 404 errors!", - "clear404-confirm": "Are you sure you wish to clear the 404 error logs?", - "clear404-success": "\"404 Not Found\" errors cleared" + "route": "Путь", + "count": "Кол-во", + "no-routes-not-found": "Ура! Ошибок 404 нет!", + "clear404-confirm": "Вы уверены, что хотите очистить журнал ошибок 404?", + "clear404-success": "Журнал ошибок 404 очищен" } \ No newline at end of file diff --git a/public/language/ru/admin/appearance/customise.json b/public/language/ru/admin/appearance/customise.json index 767d443e29..226ce535ee 100644 --- a/public/language/ru/admin/appearance/customise.json +++ b/public/language/ru/admin/appearance/customise.json @@ -1,5 +1,5 @@ { - "custom-css": "Custom CSS", + "custom-css": "Свой CSS", "custom-css.description": "Enter your own CSS declarations here, which will be applied after all other styles.", "custom-css.enable": "Enable Custom CSS", diff --git a/public/language/ru/admin/appearance/skins.json b/public/language/ru/admin/appearance/skins.json index 4db6fbdd8a..ccf0259de4 100644 --- a/public/language/ru/admin/appearance/skins.json +++ b/public/language/ru/admin/appearance/skins.json @@ -1,9 +1,9 @@ { - "loading": "Loading Skins...", - "homepage": "Homepage", - "select-skin": "Select Skin", - "current-skin": "Current Skin", - "skin-updated": "Skin Updated", - "applied-success": "%1 skin was succesfully applied", + "loading": "Загрузка стилей", + "homepage": "Домашняя страница", + "select-skin": "Выбрать стиль", + "current-skin": "Текущий стиль", + "skin-updated": "Стиль обновлен", + "applied-success": "%1 тема была успешно применена", "revert-success": "Skin reverted to base colours" } \ No newline at end of file diff --git a/public/language/ru/admin/appearance/themes.json b/public/language/ru/admin/appearance/themes.json index 3148a01337..b4d1b87426 100644 --- a/public/language/ru/admin/appearance/themes.json +++ b/public/language/ru/admin/appearance/themes.json @@ -1,11 +1,11 @@ { - "checking-for-installed": "Checking for installed themes...", - "homepage": "Homepage", - "select-theme": "Select Theme", - "current-theme": "Current Theme", - "no-themes": "No installed themes found", - "revert-confirm": "Are you sure you wish to restore the default NodeBB theme?", - "theme-changed": "Theme Changed", - "revert-success": "You have successfully reverted your NodeBB back to it's default theme.", - "restart-to-activate": "Please restart your NodeBB to fully activate this theme" + "checking-for-installed": "Проверка установленных тем", + "homepage": "Домашняя страница", + "select-theme": "Выбрать тему", + "current-theme": "Текущая тема", + "no-themes": "Не найдено установленные темы", + "revert-confirm": "Вы уверены, что хотите восстановить стандартную NodeBB тему?", + "theme-changed": "Тема сменена", + "revert-success": "Вы успешно вернули ваш NodeBB обратно к его стандартной теме.", + "restart-to-activate": "Пожалуйста, перезапустите ваш NodeBB, чтобы полностью активировать эту тему" } \ No newline at end of file diff --git a/public/language/ru/admin/development/logger.json b/public/language/ru/admin/development/logger.json index 6ab9558149..efbb214bf9 100644 --- a/public/language/ru/admin/development/logger.json +++ b/public/language/ru/admin/development/logger.json @@ -2,9 +2,9 @@ "logger-settings": "Logger Settings", "description": "By enabling the check boxes, you will receive logs to your terminal. If you specify a path, logs will then be saved to a file instead. HTTP logging is useful for collecting statistics about who, when, and what people access on your forum. In addition to logging HTTP requests, we can also log socket.io events. Socket.io logging, in combination with redis-cli monitor, can be very helpful for learning NodeBB's internals.", "explanation": "Simply check/uncheck the logging settings to enable or disable logging on the fly. No restart needed.", - "enable-http": "Enable HTTP logging", + "enable-http": "Включить HTTP логирование", "enable-socket": "Enable socket.io event logging", - "file-path": "Path to log file", + "file-path": "Путь до файла логов", "file-path-placeholder": "/path/to/log/file.log ::: leave blank to log to your terminal", "control-panel": "Logger Control Panel", diff --git a/public/language/ru/admin/extend/plugins.json b/public/language/ru/admin/extend/plugins.json index 1661a987b7..257c0b051c 100644 --- a/public/language/ru/admin/extend/plugins.json +++ b/public/language/ru/admin/extend/plugins.json @@ -1,45 +1,45 @@ { - "installed": "Installed", - "active": "Active", - "inactive": "Inactive", - "out-of-date": "Out of Date", - "none-found": "No plugins found.", - "none-active": "No Active Plugins", - "find-plugins": "Find Plugins", + "installed": "Установленные", + "active": "Активные", + "inactive": "Неактивные", + "out-of-date": "Устаревшие", + "none-found": "Плагины не найдены.", + "none-active": "Нет активных плагинов", + "find-plugins": "Найти плагины", - "plugin-search": "Plugin Search", - "plugin-search-placeholder": "Search for plugin...", - "reorder-plugins": "Re-order Plugins", - "order-active": "Order Active Plugins", - "dev-interested": "Interested in writing plugins for NodeBB?", - "docs-info": "Full documentation regarding plugin authoring can be found in the NodeBB Docs Portal.", + "plugin-search": "Поиск плагинов", + "plugin-search-placeholder": "Искать плагин...", + "reorder-plugins": "Изменить порядок плагинов", + "order-active": "Упорядочить активные плагины", + "dev-interested": "Заинтересованы в написании плагинов для NodeBB?", + "docs-info": "Полную документацию по разработки плагинов можно найти на NodeBB Docs Portal", "order.description": "Certain plugins work ideally when they are initialised before/after other plugins.", "order.explanation": "Plugins load in the order specified here, from top to bottom", - "plugin-item.themes": "Themes", + "plugin-item.themes": "Темы", "plugin-item.deactivate": "Deactivate", "plugin-item.activate": "Activate", - "plugin-item.install": "Install", - "plugin-item.uninstall": "Uninstall", - "plugin-item.settings": "Settings", - "plugin-item.installed": "Installed", - "plugin-item.latest": "Latest", - "plugin-item.upgrade": "Upgrade", + "plugin-item.install": "Установить", + "plugin-item.uninstall": "Удалить", + "plugin-item.settings": "Настройки", + "plugin-item.installed": "Установленные", + "plugin-item.latest": "Недавние", + "plugin-item.upgrade": "Обновить", "plugin-item.more-info": "For more information:", - "plugin-item.unknown": "Unknown", + "plugin-item.unknown": "Неизвестно", "plugin-item.unknown-explanation": "The state of this plugin could not be determined, possibly due to a misconfiguration error.", - "alert.enabled": "Plugin Enabled", - "alert.disabled": "Plugin Disabled", - "alert.upgraded": "Plugin Upgraded", - "alert.installed": "Plugin Installed", - "alert.uninstalled": "Plugin Uninstalled", + "alert.enabled": "Плагин включен", + "alert.disabled": "Плагин выключен", + "alert.upgraded": "Плагин обновлен", + "alert.installed": "Плагин установлен", + "alert.uninstalled": "Плагин удален", "alert.activate-success": "Please restart your NodeBB to fully activate this plugin", - "alert.deactivate-success": "Plugin successfully deactivated", - "alert.upgrade-success": "Please reload your NodeBB to fully upgrade this plugin", - "alert.install-success": "Plugin successfully installed, please activate the plugin.", - "alert.uninstall-success": "The plugin has been successfully deactivated and uninstalled.", + "alert.deactivate-success": "Плагин успешно отключен", + "alert.upgrade-success": "Пожалуйста перезапустите ваш NodeBB, чтобы полностью обновить этот плагин", + "alert.install-success": "Плагин успешно установлен, пожалуйста активируйте этот плагин.", + "alert.uninstall-success": "Плагин успешно отключен и удален.", "alert.suggest-error": "

NodeBB could not reach the package manager, proceed with installation of latest version?

Server returned (%1): %2
", "alert.package-manager-unreachable": "

NodeBB could not reach the package manager, an upgrade is not suggested at this time.

", "alert.incompatible": "

Your version of NodeBB (v%1) is only cleared to upgrade to v%2 of this plugin. Please update your NodeBB if you wish to install a newer version of this plugin.

", diff --git a/public/language/ru/admin/extend/rewards.json b/public/language/ru/admin/extend/rewards.json index 5383a90b33..64ccd4cc38 100644 --- a/public/language/ru/admin/extend/rewards.json +++ b/public/language/ru/admin/extend/rewards.json @@ -1,17 +1,17 @@ { - "rewards": "Rewards", + "rewards": "Награды", "condition-if-users": "If User's", "condition-is": "Is:", "condition-then": "Then:", "max-claims": "Amount of times reward is claimable", "zero-infinite": "Enter 0 for infinite", - "delete": "Delete", - "enable": "Enable", - "disable": "Disable", + "delete": "Удалить", + "enable": "Включить", + "disable": "Выключить", "control-panel": "Rewards Control", - "new-reward": "New Reward", + "new-reward": "Новая награда", - "alert.delete-success": "Successfully deleted reward", + "alert.delete-success": "Награды успешно удалены", "alert.no-inputs-found": "Illegal reward - no inputs found!", - "alert.save-success": "Successfully saved rewards" + "alert.save-success": "Награды успешно сохранены" } \ No newline at end of file diff --git a/public/language/ru/admin/manage/flags.json b/public/language/ru/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/ru/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/ru/admin/manage/groups.json b/public/language/ru/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/ru/admin/manage/groups.json +++ b/public/language/ru/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/ru/admin/settings/advanced.json b/public/language/ru/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/ru/admin/settings/advanced.json +++ b/public/language/ru/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/ru/admin/settings/post.json b/public/language/ru/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/ru/admin/settings/post.json +++ b/public/language/ru/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/ru/admin/settings/user.json b/public/language/ru/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/ru/admin/settings/user.json +++ b/public/language/ru/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/ru/email.json b/public/language/ru/email.json index 09daba1521..d791e5a61f 100644 --- a/public/language/ru/email.json +++ b/public/language/ru/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Вы получили это уведомление согласно вашим настройкам подписки.", "test.text1": "Это тестовое сообщение для проверки почтового сервиса.", "unsub.cta": "Изменить настройки", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Спасибо!" } \ No newline at end of file diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 7b2d73ccc4..c1aecb322e 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -30,6 +30,7 @@ "password-too-long": "Пароль слишком длинный", "user-banned": "Участник заблокирован", "user-banned-reason": "Учетная запись заблокирована (Причина: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Вы можете написать своё первое сообщение через %1 сек.", "blacklisted-ip": "Извините, ваш IP адрес был забанен этим сообществом. Если вы считаете, что это ошибка, пожалуйста, свяжитесь с администратором.", "ban-expiry-missing": "Пожалуйста, укажите дату окончания этой блокировки", @@ -104,7 +105,7 @@ "chat-disabled": "Чат выключен", "too-many-messages": "Для отправки нового сообщения необходимо подождать, т.к. вы отправили слишком много сообщений подряд.", "invalid-chat-message": "Ошибка в сообщении", - "chat-message-too-long": "Слишком длинное сообщение. Пожалуйста, сократите своё сообщение, чтобы можно было его отправить.", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "К сожалению, у вас нет доступа для редактирования этого сообщения", "cant-remove-last-user": "Удалить последнего участника невозможно.", "cant-delete-chat-message": "К сожалению, у вас нет доступа для удаления этого сообщения", diff --git a/public/language/ru/flags.json b/public/language/ru/flags.json new file mode 100644 index 0000000000..6a5776a4f2 --- /dev/null +++ b/public/language/ru/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Описание", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Обновить", + "updated": "Обновлено", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Убрать фильтры", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "Весь контент", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Категория", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "Все категории", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "Просмотреть профиль", + "start-new-chat": "Начать новый чат", + "go-to-target": "View Flag Target", + + "user-view": "Просмотреть профиль", + "user-edit": "Изменить Профиль", + + "notes": "Flag Notes", + "add-note": "Добавить примечание", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "Все государства", + "state-open": "Новый/Открыть", + "state-wip": "Work in Progress", + "state-resolved": "Решен", + "state-rejected": "Отклонен", + "no-assignee": "Не назначенный ", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Спам", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Представить отчет", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/ru/groups.json b/public/language/ru/groups.json index 9f0852bcd3..7f98491798 100644 --- a/public/language/ru/groups.json +++ b/public/language/ru/groups.json @@ -27,7 +27,7 @@ "details.disableJoinRequests": "Отключить запросы на приглашение", "details.grant": "Выдать/забрать привилегии администратора", "details.kick": "Исключить", - "details.kick_confirm": "Are you sure you want to remove this member from the group?", + "details.kick_confirm": "Вы уверены, что хотите удалить этого участника из группы?", "details.owner_options": "Настройки группы", "details.group_name": "Имя группы", "details.member_count": "Количество участников", diff --git a/public/language/ru/login.json b/public/language/ru/login.json index 4bf5fc392d..a0ab9e619b 100644 --- a/public/language/ru/login.json +++ b/public/language/ru/login.json @@ -7,6 +7,6 @@ "alternative_logins": "Войти через", "failed_login_attempt": "Неправильно указано имя пользователя или электронная почта", "login_successful": "Вы успешно вошли!", - "dont_have_account": "Нет акканута?", + "dont_have_account": "Нет аккаунта?", "logged-out-due-to-inactivity": "Вы вышли из панели управления администратора из-за бездействия" } \ No newline at end of file diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json index 2ebe6eb869..442137ca92 100644 --- a/public/language/ru/modules.json +++ b/public/language/ru/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 месяца", "chat.delete_message_confirm": "Вы уверены, что хотите удалить это сообщение?", "chat.add-users-to-room": "Добавить участников в комнату", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Редактор сообщений", "composer.show_preview": "Показать предпросмотр сообщения", "composer.hide_preview": "Скрыть предпросмотр", diff --git a/public/language/ru/search.json b/public/language/ru/search.json index dcc85a00ef..9ff5643500 100644 --- a/public/language/ru/search.json +++ b/public/language/ru/search.json @@ -12,7 +12,7 @@ "reply-count": "Количество ответов", "at-least": "Минимум", "at-most": "Максимум", - "relevance": "Relevance", + "relevance": "Релевантность", "post-time": "Время публикации", "newer-than": "Ранее чем", "older-than": "Позже чем", diff --git a/public/language/ru/user.json b/public/language/ru/user.json index ff7c1b9c73..33bd08a5b1 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -60,13 +60,14 @@ "username_taken_workaround": "Логин, который вы запросили, уже занят. Мы его немного изменили. Теперь ваш логин %1", "password_same_as_username": "Ваш пароль совпадает с именем пользователя, это очень небезопасно. Пожалуйста укажите другой пароль.", "password_same_as_email": "Ваш пароль совпадает с элетронной почтой, это очень небезопасно. Пожалуйста, укажите другой пароль.", + "weak_password": "Weak password.", "upload_picture": "Загрузить фото", "upload_a_picture": "Загрузить фото", "remove_uploaded_picture": "Удалить фото", "upload_cover_picture": "Загрузить обложку профиля", "remove_cover_picture_confirm": "Вы уверены, что хотите удалить изображение обложки?", - "crop_picture": "Crop picture", - "upload_cropped_picture": "Crop and upload", + "crop_picture": "Вырезать картинку", + "upload_cropped_picture": "Вырезать и загрузить", "settings": "Настройки", "show_email": "Показывать мою элетронную почту", "show_fullname": "Показывать полное имя", @@ -131,5 +132,5 @@ "info.email-history": "История изменения электронной почты", "info.moderation-note": "Примечание модератора", "info.moderation-note.success": "Примечание модератора сохранено", - "info.moderation-note.add": "Add note" + "info.moderation-note.add": "Добавить примечание" } \ No newline at end of file diff --git a/public/language/rw/admin/advanced/database.json b/public/language/rw/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/rw/admin/advanced/database.json +++ b/public/language/rw/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/rw/admin/manage/flags.json b/public/language/rw/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/rw/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/rw/admin/manage/groups.json b/public/language/rw/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/rw/admin/manage/groups.json +++ b/public/language/rw/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/rw/admin/settings/advanced.json b/public/language/rw/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/rw/admin/settings/advanced.json +++ b/public/language/rw/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/rw/admin/settings/post.json b/public/language/rw/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/rw/admin/settings/post.json +++ b/public/language/rw/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/rw/admin/settings/user.json b/public/language/rw/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/rw/admin/settings/user.json +++ b/public/language/rw/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/rw/email.json b/public/language/rw/email.json index d26954b90b..aca6b1b8e1 100644 --- a/public/language/rw/email.json +++ b/public/language/rw/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Iri tangazo rijyanye n'ibyashyizwe ku rubuga waryohererejwe kubera ko wabihisemo mu byo uzajya umenyeshwa", "test.text1": "Iyi message ni igerageza kugirango harebwe niba emailer ya NodeBB yarateguwe neza", "unsub.cta": "Kanda hano kugirango uhindure uko bizajya bigenda", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Murakoze!" } \ No newline at end of file diff --git a/public/language/rw/error.json b/public/language/rw/error.json index f367b35002..3e6c5cdeff 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "Umuntu wirukanwe", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Wihangena kuko usabwa gutegereza amasegonda (isegonda) %1 mbere yo gushyiraho ikintu cyawe cya mbere", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "Wohereje ubutumwa bwinshi cyane. Ba utegerejeho gato. ", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/rw/flags.json b/public/language/rw/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/rw/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json index 09d8c88fdd..8a808163d5 100644 --- a/public/language/rw/modules.json +++ b/public/language/rw/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "Amezi 3", "chat.delete_message_confirm": "Wiringiye neza ko ushaka gusiba ubu butumwa?", "chat.add-users-to-room": "Ongera abantu mu gikari", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Andika", "composer.show_preview": "Bona Uko Biza Gusa", "composer.hide_preview": "Hisha Uko Biza Gusa", diff --git a/public/language/rw/user.json b/public/language/rw/user.json index fc35772e1f..96bc2e84f9 100644 --- a/public/language/rw/user.json +++ b/public/language/rw/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Izina ushaka kujya ukoresha twasanze ryarafashwe. Ntugire impungenge kuko twakuboneye iryo byenda kumera kimwe. Uzaba uzwi ku izina rya %1", "password_same_as_username": "Ijambobanga ryawe rirasa neza n'izina ukoresha; hitamo irindi jambobanga.", "password_same_as_email": "Ijambobanga ryawe rirasa neza na email yawe; hitamo irindi jambobanga.", + "weak_password": "Weak password.", "upload_picture": "Gushyiraho ifoto", "upload_a_picture": "Shyiraho ifoto", "remove_uploaded_picture": "Kuraho Ifoto", diff --git a/public/language/sc/admin/advanced/database.json b/public/language/sc/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/sc/admin/advanced/database.json +++ b/public/language/sc/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/sc/admin/manage/flags.json b/public/language/sc/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/sc/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/sc/admin/manage/groups.json b/public/language/sc/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/sc/admin/manage/groups.json +++ b/public/language/sc/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/sc/admin/settings/advanced.json b/public/language/sc/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/sc/admin/settings/advanced.json +++ b/public/language/sc/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/sc/admin/settings/post.json b/public/language/sc/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/sc/admin/settings/post.json +++ b/public/language/sc/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/sc/admin/settings/user.json b/public/language/sc/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/sc/admin/settings/user.json +++ b/public/language/sc/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/sc/email.json b/public/language/sc/email.json index 691e6309a2..c1e17018fa 100644 --- a/public/language/sc/email.json +++ b/public/language/sc/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", "unsub.cta": "Click here to alter those settings", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Thanks!" } \ No newline at end of file diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 3149dadc15..35eaf8cbc6 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/sc/flags.json b/public/language/sc/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/sc/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json index 11a87b51d8..e360cae07e 100644 --- a/public/language/sc/modules.json +++ b/public/language/sc/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Months", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/sc/user.json b/public/language/sc/user.json index d88af312d8..b72ed9f9c2 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "The username you requested was already taken, so we have altered it slightly. You are now known as %1", "password_same_as_username": "Your password is the same as your username, please select another password.", "password_same_as_email": "Your password is the same as your email, please select another password.", + "weak_password": "Weak password.", "upload_picture": "Càrriga immàgine", "upload_a_picture": "Càrriga un'immàgine", "remove_uploaded_picture": "Remove Uploaded Picture", diff --git a/public/language/sk/admin/advanced/database.json b/public/language/sk/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/sk/admin/advanced/database.json +++ b/public/language/sk/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/sk/admin/manage/flags.json b/public/language/sk/admin/manage/flags.json index bfc488a409..4b11c7990d 100644 --- a/public/language/sk/admin/manage/flags.json +++ b/public/language/sk/admin/manage/flags.json @@ -1,14 +1,14 @@ { "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", + "by-user": "Označené používateľom", + "by-user-search": "Vyhľadávať označené príspevky podľa používateľa", "category": "Category", "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", + "sort-by.most-flags": "Najviac označené", "sort-by.most-recent": "Most Recent", "search": "Search", "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", + "none-flagged": "Žiadne označené príspevky!", "posted-in": "Posted in %1", "read-more": "Read More", "flagged-x-times": "This post has been flagged %1 time(s):", diff --git a/public/language/sk/admin/manage/groups.json b/public/language/sk/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/sk/admin/manage/groups.json +++ b/public/language/sk/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/sk/admin/manage/users.json b/public/language/sk/admin/manage/users.json index f1651a814b..1e808c70fa 100644 --- a/public/language/sk/admin/manage/users.json +++ b/public/language/sk/admin/manage/users.json @@ -10,7 +10,7 @@ "temp-ban": "Ban User(s) Temporarily", "unban": "Unban User(s)", "reset-lockout": "Reset Lockout", - "reset-flags": "Reset Flags", + "reset-flags": "Obnoviť označenia", "delete": "Delete User(s)", "purge": "Delete User(s) and Content", "download-csv": "Download CSV", @@ -23,7 +23,7 @@ "pills.top-posters": "Top Posters", "pills.top-rep": "Most Reputation", "pills.inactive": "Inactive", - "pills.flagged": "Most Flagged", + "pills.flagged": "Najviac označované", "pills.banned": "Banned", "pills.search": "User Search", diff --git a/public/language/sk/admin/settings/advanced.json b/public/language/sk/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/sk/admin/settings/advanced.json +++ b/public/language/sk/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/sk/admin/settings/post.json b/public/language/sk/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/sk/admin/settings/post.json +++ b/public/language/sk/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/sk/admin/settings/user.json b/public/language/sk/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/sk/admin/settings/user.json +++ b/public/language/sk/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/sk/email.json b/public/language/sk/email.json index 27676d7a37..11e7372009 100644 --- a/public/language/sk/email.json +++ b/public/language/sk/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Toto oznámenie o príspevkoch ste prijali na základe Vašich nastavení účtu.", "test.text1": "Toto je skúšobný e-mail na overenie funkčnosti e-mailovej aplikácie Vášho NodeBB fóra.", "unsub.cta": "Kliknite sem pre zmenu týchto nastavení", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Ďakujeme!" } \ No newline at end of file diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 93505ca3a5..76f54ce089 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -30,6 +30,7 @@ "password-too-long": "Heslo je príliš dlhé", "user-banned": "Užívateľ je zablokovaný", "user-banned-reason": "Prepáčte, tento účet bol zablokovaný (Dôvod: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Prepáčte, musíte počkať %1 sekúnd(y) predtým, ako vytvoríte svoj prvý príspevok", "blacklisted-ip": "Prepáčte, ale vaša IP adresa bola na tejto komunite zablokovaná. Ak sa cítite poškodený, prosím kontaktujte správcu.", "ban-expiry-missing": "Prosím uveďte dátum ukončenia tohto zablokovania", @@ -104,7 +105,7 @@ "chat-disabled": "Systém konverzácií je zablokovaný", "too-many-messages": "Odoslali ste príliš veľa správ, počkajte chvíľu prosím.", "invalid-chat-message": "Neplatná správa konverzácie", - "chat-message-too-long": "Správa v konverzácií je príliš dlhá", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Nemáte oprávnenie k úprave tejto správy", "cant-remove-last-user": "Nemôžete odstrániť posledného užívateľa", "cant-delete-chat-message": "Nemáte oprávanie k odstráneniu tejto správy", diff --git a/public/language/sk/flags.json b/public/language/sk/flags.json new file mode 100644 index 0000000000..21bdc1bf31 --- /dev/null +++ b/public/language/sk/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hurá! Žiadne označenia neboli nájdené.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Označený používateľ", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json index 191d9f2abb..880a4a0128 100644 --- a/public/language/sk/modules.json +++ b/public/language/sk/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 mesiace", "chat.delete_message_confirm": "Ste si istý, že chcete odstrániť túto správu?", "chat.add-users-to-room": "Pridať užívateľa do miestnosti", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Zostaviť", "composer.show_preview": "Zobraziť náhľad", "composer.hide_preview": "Skryť náhľad", diff --git a/public/language/sk/notifications.json b/public/language/sk/notifications.json index 39ca363782..5bf882e4d1 100644 --- a/public/language/sk/notifications.json +++ b/public/language/sk/notifications.json @@ -16,7 +16,7 @@ "chat": "Konverzácie", "follows": "Nasledovatelia", "upvote": "Zahlasované", - "new-flags": "New Flags", + "new-flags": "Nové označenia", "my-flags": "Flags assigned to me", "bans": "Zablokované", "new_message_from": "Nova spáva od %1", diff --git a/public/language/sk/user.json b/public/language/sk/user.json index 98e87e05f3..b072831a64 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Vaše požadované prihlasovacie meno je už obsadené, tak sme si ho dovolili mierne upraviť. Budeme Vás evidovať ako %1", "password_same_as_username": "Vaše heslo sa zhoduje s Vaším používateľským menom, prosím zvoľte iné heslo.", "password_same_as_email": "Vaše heslo sa zhoduje s Vaším e-mailom, prosím zvoľte iné heslo.", + "weak_password": "Slabé heslo.", "upload_picture": "Nahrať obrázok", "upload_a_picture": "Nahrať obrázok", "remove_uploaded_picture": "Vymazať nahraný obrázok", diff --git a/public/language/sl/admin/advanced/database.json b/public/language/sl/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/sl/admin/advanced/database.json +++ b/public/language/sl/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/sl/admin/manage/flags.json b/public/language/sl/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/sl/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/sl/admin/manage/groups.json b/public/language/sl/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/sl/admin/manage/groups.json +++ b/public/language/sl/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/sl/admin/settings/advanced.json b/public/language/sl/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/sl/admin/settings/advanced.json +++ b/public/language/sl/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/sl/admin/settings/post.json b/public/language/sl/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/sl/admin/settings/post.json +++ b/public/language/sl/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/sl/admin/settings/user.json b/public/language/sl/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/sl/admin/settings/user.json +++ b/public/language/sl/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/sl/email.json b/public/language/sl/email.json index 768c9c03c2..7934ff9bfb 100644 --- a/public/language/sl/email.json +++ b/public/language/sl/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Obvestilo o objavi vam je bilo poslano zaradi nastavitev vaše naročnine.", "test.text1": "To je testno elektronsko sporočilo za preverjanje pravilnosti nastavitev podsistema za pošiljanje NodeBB poštnih sporočil.", "unsub.cta": "Kliknite tu za spremembo nastavitev.", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Hvala!" } \ No newline at end of file diff --git a/public/language/sl/error.json b/public/language/sl/error.json index 00452a7de0..6c876e99d4 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -30,6 +30,7 @@ "password-too-long": "Geslo je predolgo.", "user-banned": "Uporabnik je izločen.", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": " Pred svojo prvo objavo počakajte %1 s.", "blacklisted-ip": "Vaš IP-naslov je izločen. Povprašajte skrbnika za več informacij.", "ban-expiry-missing": "Vnesite končni datum za to izločitev.", @@ -104,7 +105,7 @@ "chat-disabled": "Klepet je onemogočen.", "too-many-messages": "Poslali ste preveč sporočil, prosimo, počakajte nekaj časa.", "invalid-chat-message": "Neveljavno sporočilo klepeta", - "chat-message-too-long": "Sporočilo klepeta je predolgo.", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Nimate dovoljenja za urejanje tega sporočila.", "cant-remove-last-user": "Zadnjega uporabnika ne morete odstraniti.", "cant-delete-chat-message": "NImate dovoljenja za izbris tega sporočila.", diff --git a/public/language/sl/flags.json b/public/language/sl/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/sl/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json index 860b680e7e..18b2281ba4 100644 --- a/public/language/sl/modules.json +++ b/public/language/sl/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 meseci", "chat.delete_message_confirm": "Ali ste prepričani, da želite izbrisati to sporočilo?", "chat.add-users-to-room": "Dodaj uporabnike v sobo.", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Sestavljanje", "composer.show_preview": "Pokaži predogled", "composer.hide_preview": "Skrij predogled", diff --git a/public/language/sl/user.json b/public/language/sl/user.json index 479046c340..bdb0cba806 100644 --- a/public/language/sl/user.json +++ b/public/language/sl/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Predlagano uporabniško ime je že zasedeno, zato smo ga rahlo spremenili. Sedaj vas poznamo kot %1", "password_same_as_username": "Vaše geslo je enako kot vaše uporabniško ime, prosim izberite drugačno geslo.", "password_same_as_email": "Vaše geslo je enako kot vaše e-poštni naslov, prosim izberite drugačno geslo.", + "weak_password": "Weak password.", "upload_picture": "Naloži fotografijo", "upload_a_picture": "Naloži fotografijo", "remove_uploaded_picture": "Odstrani preneseno sliko ", diff --git a/public/language/sr/admin/advanced/database.json b/public/language/sr/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/sr/admin/advanced/database.json +++ b/public/language/sr/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/sr/admin/manage/flags.json b/public/language/sr/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/sr/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/sr/admin/manage/groups.json b/public/language/sr/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/sr/admin/manage/groups.json +++ b/public/language/sr/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/sr/admin/settings/advanced.json b/public/language/sr/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/sr/admin/settings/advanced.json +++ b/public/language/sr/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/sr/admin/settings/post.json b/public/language/sr/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/sr/admin/settings/post.json +++ b/public/language/sr/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/sr/admin/settings/user.json b/public/language/sr/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/sr/admin/settings/user.json +++ b/public/language/sr/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/sr/email.json b/public/language/sr/email.json index d47a4abd13..c69628cbbf 100644 --- a/public/language/sr/email.json +++ b/public/language/sr/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Ово обавештење вам је послато услед вашег подешавања претплате.", "test.text1": "Ово је пробно е-писмо за проверу исправности поставки е-поштара у NodeBB.", "unsub.cta": "Кликните овде да измените та подешавања", + "banned.subject": "Забрањени сте на %1", + "banned.text1": "Корисник %1 је забрањен на %2.", + "banned.text2": "Ова забрана ће трајати до %1.", + "banned.text3": "Ово је разлог зашто сте забрањени:", "closing": "Хвала!" } \ No newline at end of file diff --git a/public/language/sr/error.json b/public/language/sr/error.json index fb4e7993a4..f05560c926 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -30,6 +30,7 @@ "password-too-long": "Шифра је предугачка.", "user-banned": "Корисник је забрањен", "user-banned-reason": "Овај налог је забрањен (Разлог: %1)", + "user-banned-reason-until": "Овај налог је забрањен до %1 (Разлог: %2)", "user-too-new": "Жао нам је, морате сачекати %1 секунде/и пре него што објавите прву поруку", "blacklisted-ip": "Жао нам је, ваша IP је забрањена у овој заједници. Ако мислите да је ово грешка, контактирајте администратора.", "ban-expiry-missing": "Наведите крајњи датум за ову забрану", @@ -58,15 +59,15 @@ "post-delete-duration-expired-days-hours": "Време у којем вам је дозвољено брисање порука након објављивања: %1 дан. и %2 час.", "cant-delete-topic-has-reply": "Не можете обрисати вашу тему након што је на њу одговорено", "cant-delete-topic-has-replies": "Не можете обрисати вашу тему након што добије %1 одговора", - "content-too-short": "Унесите дужу поруку. Порука мора имати најмање %1 карактера.", - "content-too-long": "Унесите краћу поруку. Порука не сме бити дужа од %1 карактера.", - "title-too-short": "Унесите дужи наслов. Наслов мора имати најмање %1 карактера.", - "title-too-long": "Унесите краћи наслов. Наслов не сме бити дужи од %1 карактера.", + "content-too-short": "Унесите дужу поруку. Порука мора садржати најмање %1 знак(ов)а.", + "content-too-long": "Унесите краћу поруку. Порука не сме бити дужа од %1 знак(ов)а.", + "title-too-short": "Унесите дужи наслов. Наслов мора садржати најмање %1 знак(ов)а.", + "title-too-long": "Унесите краћи наслов. Наслов не сме бити дужи од %1 знак(ов)а.", "category-not-selected": "Није одабрана категорија", "too-many-posts": "Можете објављивати поруке само једном у %1 секунди - сачекајте пре него што покушате поново", "too-many-posts-newbie": "Као нови корисник, можете објављивати поруке само једном у %1 секунди док не достигнете %2 углед - сачекајте пре него што покушате поново", - "tag-too-short": "Унесите дужу ознаку. Ознаке морају имати најмање %1 карактера.", - "tag-too-long": "Унесите краћу ознаку. Ознаке не смеју бити дуже од %1 карактера.", + "tag-too-short": "Унесите дужу ознаку. Ознаке морају садржати најмање %1 знак(ов)а.", + "tag-too-long": "Унесите краћу ознаку. Ознаке не смеју бити дуже од %1 знак(ов)а.", "not-enough-tags": "Нема довољно ознака. Теме морају имати најмање %1 ознаке/а.", "too-many-tags": "Превише ознака. Теме не смеју имати више од %1 ознаке/а.", "still-uploading": "Сачекајте док се отпремања не заврше.", @@ -97,14 +98,14 @@ "topic-thumbnails-are-disabled": "Сличице тема су онемогућене.", "invalid-file": "Неисправна датотека", "uploads-are-disabled": "Отпремања су онемогућена", - "signature-too-long": "Жао нам је, потпис не сме бити дужи од %1 карактера", - "about-me-too-long": "Жао нам је, информације о вама не смеју бити дуже од %1 карактера ", + "signature-too-long": "Жао нам је, потпис не сме бити дужи од %1 знак(ов)а.", + "about-me-too-long": "Жао нам је, информације о вама не смеју бити дуже од %1 знак(ов)а.", "cant-chat-with-yourself": "Не можете ћаскати са самим собом!", "chat-restricted": "Овај корисник је ограничио њихова ћаскања. Морају вас пратити пре него што можете ћаскати са њима.", "chat-disabled": "Ћаскања су онемогућена", "too-many-messages": "Послали сте превише порука, сачекајте мало.", "invalid-chat-message": "Неважећа порука", - "chat-message-too-long": "Порука је предугачка", + "chat-message-too-long": "Поруке ћаскања не могу бити дуже од %1 знакова.", "cant-edit-chat-message": "Није вам дозвољено да уређујете ову поруку", "cant-remove-last-user": "Не можете уклонити последњег корисника", "cant-delete-chat-message": "Није вам дозвољено да избришете ову поруку", diff --git a/public/language/sr/flags.json b/public/language/sr/flags.json new file mode 100644 index 0000000000..e60225af70 --- /dev/null +++ b/public/language/sr/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Стање", + "reporter": "Извештач", + "reported-at": "Пријављено", + "description": "Опис", + "no-flags": "Ура! Нема заставица.", + "assignee": "Заступник", + "update": "Ажурирај", + "updated": "Ажурирано", + "target-purged": "Садржај на који се односи ова заставица је очишћен и није више доступан.", + + "quick-filters": "Брзи филтери", + "filter-active": "Постоји један или више активних филтера на овом списку заставица", + "filter-reset": "Уклони заставице", + "filters": "Опције филтера", + "filter-reporterId": "UID извештача", + "filter-targetUid": "UID означеног", + "filter-type": "Тип заставице", + "filter-type-all": "Сав садржај", + "filter-type-post": "Порука", + "filter-state": "Стање", + "filter-assignee": "UID заступника", + "filter-cid": "Категорија", + "filter-quick-mine": "Додељено мени", + "filter-cid-all": "Све категорије", + "apply-filters": "Примени филтере", + + "quick-links": "Брзе везе", + "flagged-user": "Означени корисник", + "view-profile": "Погледај профил", + "start-new-chat": "Започни ново ћаскање", + "go-to-target": "Погледај циљ означавања", + + "user-view": "Погледај профил", + "user-edit": "Уреди профил", + + "notes": "Белешке о заставицама", + "add-note": "Додај белешку", + "no-notes": "Нема дељених бележака.", + + "history": "Историја заставица", + "back": "Назад на списак заставица", + "no-history": "Нема историје заставица", + + "state-all": "Сва стања", + "state-open": "Ново/Отвори", + "state-wip": "Рад у току", + "state-resolved": "Решено", + "state-rejected": "Одбијено", + "no-assignee": "Недодељено", + "note-added": "Белешка је додата", + + "modal-title": "Пријави неприкладан садржај", + "modal-body": "Наведите разлог за означавање %1 %2 за проверу. Алтернативно, користите један од тастера за брзу пријаву ко је применљиво.", + "modal-reason-spam": "Непожељно", + "modal-reason-offensive": "Увредљиво", + "modal-reason-custom": "Разлог за пријаву овог садржаја...", + "modal-submit": "Пошаљи извештај", + "modal-submit-success": "Садржај је означен за модерацију." +} \ No newline at end of file diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json index 2c4b9cb728..156f96ec4b 100644 --- a/public/language/sr/modules.json +++ b/public/language/sr/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 месеца", "chat.delete_message_confirm": "Да ли сте сигурни да желите да избришете ову поруку?", "chat.add-users-to-room": "Додајте кориснике у собу", + "chat.confirm-chat-with-dnd-user": "Овај корисник је поставио свој статус на \"Не узнемиравај\". Да ли и даље желите да ћаскате са њим?", "composer.compose": "Писање поруке", "composer.show_preview": "Прикажи преглед", "composer.hide_preview": "Сакриј преглед", diff --git a/public/language/sr/user.json b/public/language/sr/user.json index f65600abaf..3949589e7b 100644 --- a/public/language/sr/user.json +++ b/public/language/sr/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Корисничко име које сте захтевали је већ заузето па смо је мало изменили. Сада сте знани као %1", "password_same_as_username": "Ваша лозинка је иста као ваше име, изаберите другу лозинку", "password_same_as_email": "Ваша лозинка је иста као ваша е-пошта, изаберите другу лозинку", + "weak_password": "Лозинка је слаба", "upload_picture": "Отпремање слике", "upload_a_picture": "Отпреми слику", "remove_uploaded_picture": "Уклоните отпремљену слику", diff --git a/public/language/sv/admin/advanced/database.json b/public/language/sv/admin/advanced/database.json index f7db6220ee..b88ca6fc82 100644 --- a/public/language/sv/admin/advanced/database.json +++ b/public/language/sv/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Uptime in Seconds", "uptime-days": "Uptime in Days", diff --git a/public/language/sv/admin/manage/flags.json b/public/language/sv/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/sv/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/sv/admin/manage/groups.json b/public/language/sv/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/sv/admin/manage/groups.json +++ b/public/language/sv/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/sv/admin/settings/advanced.json b/public/language/sv/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/sv/admin/settings/advanced.json +++ b/public/language/sv/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/sv/admin/settings/post.json b/public/language/sv/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/sv/admin/settings/post.json +++ b/public/language/sv/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/sv/admin/settings/user.json b/public/language/sv/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/sv/admin/settings/user.json +++ b/public/language/sv/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/sv/email.json b/public/language/sv/email.json index 6780dc6f92..db54326e29 100644 --- a/public/language/sv/email.json +++ b/public/language/sv/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Det här meddelandet fick du på grund av dina inställningar för prenumeration. ", "test.text1": "\nDet här är ett testmeddelande som verifierar att e-posten är korrekt installerad för din NodeBB. ", "unsub.cta": "Klicka här för att ändra inställningarna", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Tack!" } \ No newline at end of file diff --git a/public/language/sv/error.json b/public/language/sv/error.json index f976d6e657..2e3ea4bfcb 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -30,6 +30,7 @@ "password-too-long": "Lösenordet är för långt", "user-banned": "Användare bannlyst", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "När du är ny medlem måste du vänta %1 sekund(er) innan du gör ditt första inlägg", "blacklisted-ip": "Din IP-adress har blivit bannlyst från det här forumet. Om du tror att det beror på ett misstag, vad god kontakta en administratör. ", "ban-expiry-missing": "Ange ett slutdatum för denna banning", @@ -104,7 +105,7 @@ "chat-disabled": "Chatten är inaktiverad", "too-many-messages": "Du har skickat för många meddelanden, var god vänta", "invalid-chat-message": "Ogiltigt chattmeddelande", - "chat-message-too-long": "Chattmeddelande är för långt", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Du har inte rättigheter att redigera det här meddelandet", "cant-remove-last-user": "Du kan inte ta bort den sista användaren", "cant-delete-chat-message": "Du har inte rättigheter att radera det här meddelandet", diff --git a/public/language/sv/flags.json b/public/language/sv/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/sv/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json index d0bb0ec1ea..17a3c003a0 100644 --- a/public/language/sv/modules.json +++ b/public/language/sv/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 månader", "chat.delete_message_confirm": "Är du säker på att du vill radera det här meddelandet?", "chat.add-users-to-room": "Addera användare till rum", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Komponera", "composer.show_preview": "Visa förhandsgranskning", "composer.hide_preview": "Dölj förhandsgranskning", diff --git a/public/language/sv/user.json b/public/language/sv/user.json index bffff33e87..2c5494af3b 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Användarnamnet är redan upptaget, så vi förändrade det lite. Du kallas nu för %1", "password_same_as_username": "Ditt lösenord är samma som ditt användarnamn, välj ett annat lösenord.", "password_same_as_email": "Ditt lösenord är detsamma som din e-postadress. Var god välj ett annat lösenord.", + "weak_password": "Weak password.", "upload_picture": "Ladda upp bild", "upload_a_picture": "Ladda upp en bild", "remove_uploaded_picture": "Ta bort uppladdad bild", diff --git a/public/language/th/admin/advanced/database.json b/public/language/th/admin/advanced/database.json index eef9a4e6d4..ee4f15b2b6 100644 --- a/public/language/th/admin/advanced/database.json +++ b/public/language/th/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "ระยะเวลาทำงานต่อเนื่องเป็นวินาที", "uptime-days": "ระยะเวลาทำงานต่อเนื่องเป็นวัน", diff --git a/public/language/th/admin/manage/flags.json b/public/language/th/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/th/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/th/admin/manage/groups.json b/public/language/th/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/th/admin/manage/groups.json +++ b/public/language/th/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/th/admin/settings/advanced.json b/public/language/th/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/th/admin/settings/advanced.json +++ b/public/language/th/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/th/admin/settings/post.json b/public/language/th/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/th/admin/settings/post.json +++ b/public/language/th/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/th/admin/settings/user.json b/public/language/th/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/th/admin/settings/user.json +++ b/public/language/th/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/th/email.json b/public/language/th/email.json index 6881135565..f6e3cd7e5b 100644 --- a/public/language/th/email.json +++ b/public/language/th/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", "test.text1": "นี่คืออีเมลทดสอบเพื่อยืนยันว่าระบบอีเมลมีการตั้งค่าที่ถูกต้องสำหรับ NodeBB ของคุณ", "unsub.cta": "กดตรงนี้เพื่อเปลี่ยนแปลงการตั้งค่า", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "ขอบคุณ!" } \ No newline at end of file diff --git a/public/language/th/error.json b/public/language/th/error.json index 96df4a204c..4d5cf25465 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -30,6 +30,7 @@ "password-too-long": "Password too long", "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", @@ -104,7 +105,7 @@ "chat-disabled": "Chat system disabled", "too-many-messages": "You have sent too many messages, please wait awhile.", "invalid-chat-message": "Invalid chat message", - "chat-message-too-long": "Chat message is too long", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "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", diff --git a/public/language/th/flags.json b/public/language/th/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/th/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/th/modules.json b/public/language/th/modules.json index 59c3a2e66e..00a675c2f2 100644 --- a/public/language/th/modules.json +++ b/public/language/th/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Months", "chat.delete_message_confirm": "Are you sure you wish to delete this message?", "chat.add-users-to-room": "Add users to room", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Compose", "composer.show_preview": "Show Preview", "composer.hide_preview": "Hide Preview", diff --git a/public/language/th/user.json b/public/language/th/user.json index c6dc2304d1..345583ac68 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "ชื้อผู้ใช้นี้ถูกใช้แล้ว เราทำการแก้ไขชื่อผู้ใช้ของคุณเล็กน้อยเป็น %1", "password_same_as_username": "คุณใช้รหัสผ่านเดียวกับชื่อผู้ใช้ กรุณาเปลี่ยนรหัสผ่านใหม่", "password_same_as_email": "คุณใช้รหัสผ่านเดียวกับอีเมล กรุณาเปลี่ยนรหัสผ่านใหม่", + "weak_password": "Weak password.", "upload_picture": "อัพโหลดรูป", "upload_a_picture": "อัพโหลดรูป", "remove_uploaded_picture": "ลบภาพที่อัพโหลดไว้", diff --git a/public/language/tr/admin/advanced/database.json b/public/language/tr/admin/advanced/database.json index 2d7196eebd..25989418d7 100644 --- a/public/language/tr/admin/advanced/database.json +++ b/public/language/tr/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Saniyede Bir Çalışma Zamanı", "uptime-days": "Günde Bir Çalışma Zamanı", diff --git a/public/language/tr/admin/manage/flags.json b/public/language/tr/admin/manage/flags.json deleted file mode 100644 index aecd180ada..0000000000 --- a/public/language/tr/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Günlük Bayraklar", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Kategori", - "sort-by": "Sıralama", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Ara", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/tr/admin/manage/groups.json b/public/language/tr/admin/manage/groups.json index fff1ef9df0..f076da3bde 100644 --- a/public/language/tr/admin/manage/groups.json +++ b/public/language/tr/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Grup Adı", "description": "Grup Açıklaması", + "member-count": "Member Count", "system": "System Group", "edit": "Düzenle", "search-placeholder": "Ara", diff --git a/public/language/tr/admin/settings/advanced.json b/public/language/tr/admin/settings/advanced.json index ed8438eb34..fb9f6cd486 100644 --- a/public/language/tr/admin/settings/advanced.json +++ b/public/language/tr/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/tr/admin/settings/chat.json b/public/language/tr/admin/settings/chat.json index a695d56607..384c27fb12 100644 --- a/public/language/tr/admin/settings/chat.json +++ b/public/language/tr/admin/settings/chat.json @@ -4,6 +4,6 @@ "disable-editing": "Sohbet mesajlarını düzenlemeyi/silmeyi kapat", "disable-editing-help": "Administrators and global moderators are exempt from this restriction", "max-length": "Maksimum sohbet mesajı uzunluğu", - "max-room-size": "Maximum number of users in chat rooms", + "max-room-size": "Sohbet odalarındaki maksimum kullanıcı sayısı", "delay": "Time between chat messages in milliseconds" } \ No newline at end of file diff --git a/public/language/tr/admin/settings/post.json b/public/language/tr/admin/settings/post.json index 71d021e580..1a4cb89f77 100644 --- a/public/language/tr/admin/settings/post.json +++ b/public/language/tr/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/tr/admin/settings/user.json b/public/language/tr/admin/settings/user.json index dd7154fc35..0aa97f45e8 100644 --- a/public/language/tr/admin/settings/user.json +++ b/public/language/tr/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/tr/email.json b/public/language/tr/email.json index 4353efd42a..4cf508e292 100644 --- a/public/language/tr/email.json +++ b/public/language/tr/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Bu yazı bildirimi size abonelik ayarlarınız nedeni ile gönderilmiştir.", "test.text1": "Bu ileti NodeBB e-posta ayarlarınızın doğru çalışıp çalışmadığını kontrol etmek için gönderildi.", "unsub.cta": "Buraya tıklayarak ayarlarınızı değiştirebilirsiniz.", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Teşekkürler!" } \ No newline at end of file diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 457af00f3e..16c3a33815 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -30,6 +30,7 @@ "password-too-long": "Parola çok uzun", "user-banned": "Kullanıcı Yasaklı", "user-banned-reason": "Maalesef, bu hesap yasaklandı (Sebep:% 1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Özür dileriz, ilk iletinizi yapmadan önce %1 saniye beklemeniz gerekiyor", "blacklisted-ip": "Üzgünüz, IP adresiniz, bu toplulukta yasaklandı. Bunun bir hata olduğunu düşünüyorsanız, bir yönetici ile irtibata geçiniz.", "ban-expiry-missing": "Bu yasak için bir bitiş tarihi girin", @@ -104,7 +105,7 @@ "chat-disabled": "Sohbet özelliği kapalı", "too-many-messages": "Ardı ardına çok fazla mesaj yolladınız, lütfen biraz bekleyiniz.", "invalid-chat-message": "Geçersiz sohbet mesajı", - "chat-message-too-long": "Sohbet mesajı çok uzun", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Bu mesajı düzenlemek için izin verilmez", "cant-remove-last-user": "Son kullanıcıyı silemezsiniz", "cant-delete-chat-message": "Bu mesajı silmek için izin verilmez", diff --git a/public/language/tr/flags.json b/public/language/tr/flags.json new file mode 100644 index 0000000000..612ccb6500 --- /dev/null +++ b/public/language/tr/flags.json @@ -0,0 +1,60 @@ +{ + "state": "Bildiri", + "reporter": "Muhabir", + "reported-at": "Bildirildi", + "description": "Açıklama", + "no-flags": "Yaşasın! Bayrak bulunamadı.", + "assignee": "Vekil", + "update": "Güncelle", + "updated": "Güncellendi", + "target-purged": "İlgili bayrağın içeriği temizlendi ve artık mevcut değil.", + + "quick-filters": "Akıllı Filtre", + "filter-active": "Bayraklar listesinde etkin olan bir veya daha fazla filtre var", + "filter-reset": "Filtreleri Kaldır", + "filters": "Filtre Ayarı", + "filter-reporterId": "Muhabir UID", + "filter-targetUid": "Bayraklanan UID", + "filter-type": "Bayrak Tipi", + "filter-type-all": "Bütün İçerik", + "filter-type-post": "İleti", + "filter-state": "Bildiri", + "filter-assignee": "Vekil UID", + "filter-cid": "Kategori", + "filter-quick-mine": "Vekil atandı", + "filter-cid-all": "Bütün Kategoriler", + "apply-filters": "Filtreleri Onayla", + + "quick-links": "Akıllı Bağlantılar", + "flagged-user": "Bayraklanan Kullanıcı", + "view-profile": "Profili Gör", + "start-new-chat": "Yeni Sohbet Başlat", + "go-to-target": "Bayrak Hedefini Gör", + + "user-view": "Profili Gör", + "user-edit": "Profili Düzenle", + + "notes": "Bayrak Notu", + "add-note": "Not Ekle", + "no-notes": "Not paylaşılmadı", + + "history": "Bayrak Geçmişi", + "back": "Bayrak Listesine Geri Dön", + "no-history": "Bayrak geçmişi yok", + + "state-all": "Bütün Bildiriler", + "state-open": "Yeni/Açık", + "state-wip": "Yapım Aşamasında", + "state-resolved": "Çözüldü", + "state-rejected": "Reddedildi", + "no-assignee": "Atanmadı", + "note-added": "Not eklendi", + + "modal-title": "Uygunsuz İçeriği Rapor Et", + "modal-body": "%1 %2 için bayraklama nedenini belirtin. Alternatif olarak hızlı rapor butonlarından birinini kullanabilirsin.", + "modal-reason-spam": "Gereksiz", + "modal-reason-offensive": "Saldırgan", + "modal-reason-custom": "Bir içeriği bildirme nedeni...", + "modal-submit": "Raporu Gönder", + "modal-submit-success": "İçerik, denetlemek için bayraklandı." +} \ No newline at end of file diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json index e0b251b7bc..a96b07be46 100644 --- a/public/language/tr/modules.json +++ b/public/language/tr/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 Ay", "chat.delete_message_confirm": "Bu mesajı silmek istediğinden emin misin?", "chat.add-users-to-room": "Odaya Kullanıcı Ekle", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Yaz", "composer.show_preview": "Önizleme Göster", "composer.hide_preview": "Önizleme Sakla", diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json index dc0383981e..b4e317f224 100644 --- a/public/language/tr/notifications.json +++ b/public/language/tr/notifications.json @@ -17,7 +17,7 @@ "follows": "Takip ediyor", "upvote": "Artı Oy", "new-flags": "Yeni Bayrak", - "my-flags": "Flags assigned to me", + "my-flags": "Vekil olarak atandığım bayraklar", "bans": "Yasaklamalar", "new_message_from": "%1 size bir mesaj gönderdi", "upvoted_your_post_in": "%1 iletinizi beğendi. %2.", diff --git a/public/language/tr/user.json b/public/language/tr/user.json index 05dcd10d0a..f6ac9536a9 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "İstediğiniz kullanıcı ismi zaten alınmış, bu yüzden biraz degiştirdik. Şimdiki kullanıcı isminiz %1", "password_same_as_username": "Parolanız kullanıcı adınız ile aynı, lütfen başka bir parola seçiniz.", "password_same_as_email": "Şifreniz mail adresiniz ile aynı lütfen başka bir şifre seçin.", + "weak_password": "Zayıf parola.", "upload_picture": "Resim Yükle", "upload_a_picture": "Bir Resim Yükle", "remove_uploaded_picture": "Yüklenmiş fotoğrafı kaldır", diff --git a/public/language/uk/admin/advanced/database.json b/public/language/uk/admin/advanced/database.json index b7ff1ae639..66b2956c90 100644 --- a/public/language/uk/admin/advanced/database.json +++ b/public/language/uk/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 б", "x-mb": "%1 мб", + "x-gb": "%1 gb", "uptime-seconds": "Uptime в секундах", "uptime-days": "Uptime в днях", diff --git a/public/language/uk/admin/manage/flags.json b/public/language/uk/admin/manage/flags.json deleted file mode 100644 index df1fcb01cb..0000000000 --- a/public/language/uk/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Щоденні скарги", - "by-user": "Скарги за користувачем", - "by-user-search": "Пошук оскаржених постів за іменем користувача", - "category": "Категорія", - "sort-by": "Сортувати за", - "sort-by.most-flags": "Найбільше скарг", - "sort-by.most-recent": "Найсвіжіші", - "search": "Шукати", - "dismiss-all": "Відхилити всі", - "none-flagged": "Скарг немає!", - "posted-in": "Запощено в %1", - "read-more": "Читати далі", - "flagged-x-times": "Цей пост було оскаржено %1 раз(ів):", - "dismiss": "Відхилити цю скаргу", - "delete-post": "Видалити цей пост", - - "alerts.confirm-delete-post": "Ви точно бажаєте видалити цей пост?" -} \ No newline at end of file diff --git a/public/language/uk/admin/manage/groups.json b/public/language/uk/admin/manage/groups.json index 8287ef6da8..9758b901c8 100644 --- a/public/language/uk/admin/manage/groups.json +++ b/public/language/uk/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Назва групи", "description": "Опис групи", + "member-count": "Member Count", "system": "Системна група", "edit": "Редагувати", "search-placeholder": "Пошук", diff --git a/public/language/uk/admin/settings/advanced.json b/public/language/uk/admin/settings/advanced.json index 6f27dfebd2..4f96f25c9b 100644 --- a/public/language/uk/admin/settings/advanced.json +++ b/public/language/uk/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Задати ALLOW-FROM для розміщення NodeBB в iFrame", "headers.powered-by": "Налаштувати заголовок \"Powered By\", котрий відправляє NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "Щоб заборонити доступ для всіх сайтів, залиште пустим або вкажіть null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Керування трафіком", diff --git a/public/language/uk/admin/settings/post.json b/public/language/uk/admin/settings/post.json index 77034cbf9c..ea770e1463 100644 --- a/public/language/uk/admin/settings/post.json +++ b/public/language/uk/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Налаштування непрочитаних", "unread.cutoff": "За скільки днів показувати непрочитані", "unread.min-track-last": "Мінімальна кількість постів у темі перш ніж відслідковувати останні прочитані", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Налаштування підписів", "signature.disable": "Вимкнути підписи", "signature.no-links": "Вимкнути посилання в підписах", diff --git a/public/language/uk/admin/settings/user.json b/public/language/uk/admin/settings/user.json index 263df1745b..dc463f9ecd 100644 --- a/public/language/uk/admin/settings/user.json +++ b/public/language/uk/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Мінімальна довжина імені користувача", "max-username-length": "Максимальна довжина імені користувача", "min-password-length": "Мінімальна довжина пароля", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Максимальна довжина розділу \"Про мене\"", "terms-of-use": "Умови користування форумом (Залиште пустим, щоб вимкнути)", "user-search": "Пошук користувачів", diff --git a/public/language/uk/email.json b/public/language/uk/email.json index 386af1ca97..236a1f3978 100644 --- a/public/language/uk/email.json +++ b/public/language/uk/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Це поштове повідомлення було вислано вам, згідно ваших налаштувань підписки", "test.text1": "Це пробний лист для верифікації поштової служби. Всі налаштування вірні для NodeBB.", "unsub.cta": "Натисніть тут, щоб змінити ці налаштування", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Дякуємо!" } \ No newline at end of file diff --git a/public/language/uk/error.json b/public/language/uk/error.json index 286eb36767..2cc9650c8e 100644 --- a/public/language/uk/error.json +++ b/public/language/uk/error.json @@ -30,6 +30,7 @@ "password-too-long": "Пароль задовгий", "user-banned": "Користувача забанено", "user-banned-reason": "Вибачте, але цей акаунт було забанено (Причина: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Вибачте, але вам необхідно зачекати %1 секунд(и), перед першим постом", "blacklisted-ip": "Вибачте, але ваша IP-адреса була забанена в цій спільноті. Якщо ви гадаєте, що це сталось помилково, зв'яжіться з адміністратором.", "ban-expiry-missing": "Вкажіть, будь ласка, кінцеву дату бану", @@ -104,7 +105,7 @@ "chat-disabled": "Чат вимкнено", "too-many-messages": "Ви надіслали забагато повідомлень, зачекайте трішки.", "invalid-chat-message": "Невірне повідомлення чату", - "chat-message-too-long": "Повідомлення чату задовге", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Ви не можете редагувати повідомлення", "cant-remove-last-user": "Ви не можете видалити останнього користувача", "cant-delete-chat-message": "Ви не можете видалити це повідомлення", diff --git a/public/language/uk/modules.json b/public/language/uk/modules.json index f005377372..6cb7697cc5 100644 --- a/public/language/uk/modules.json +++ b/public/language/uk/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 місяці", "chat.delete_message_confirm": "Ви впевнені, що хочете видалити це повідомлення?", "chat.add-users-to-room": "Додати користувачів до кімнати", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Редактор повідомлень", "composer.show_preview": "Показати попередній перегляд", "composer.hide_preview": "Сховати попередній перегляд", diff --git a/public/language/uk/user.json b/public/language/uk/user.json index bad807379e..0d5e93857f 100644 --- a/public/language/uk/user.json +++ b/public/language/uk/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Ім'я користувача, що ви обрали, вже було зайняте, то ж ми його трішки змінили. Ви тепер відомі як %1", "password_same_as_username": "Ваш пароль співпадає з іменем користувача. Оберіть інший пароль, будь ласка.", "password_same_as_email": "Ваш пароль співпадає з електронною поштою. Оберіть інший пароль, будь ласка.", + "weak_password": "Weak password.", "upload_picture": "Завантажити зображення", "upload_a_picture": "Завантажити зображення", "remove_uploaded_picture": "Видалити завантажене зображення", diff --git a/public/language/vi/admin/advanced/database.json b/public/language/vi/admin/advanced/database.json index fe446d1759..7dd15423dc 100644 --- a/public/language/vi/admin/advanced/database.json +++ b/public/language/vi/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "Thời gian hoạt động(giây)", "uptime-days": "Thời gian hoạt động(Ngày)", diff --git a/public/language/vi/admin/manage/flags.json b/public/language/vi/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/vi/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/vi/admin/manage/groups.json b/public/language/vi/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/vi/admin/manage/groups.json +++ b/public/language/vi/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/vi/admin/settings/advanced.json b/public/language/vi/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/vi/admin/settings/advanced.json +++ b/public/language/vi/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/vi/admin/settings/post.json b/public/language/vi/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/vi/admin/settings/post.json +++ b/public/language/vi/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/vi/admin/settings/user.json b/public/language/vi/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/vi/admin/settings/user.json +++ b/public/language/vi/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/vi/email.json b/public/language/vi/email.json index 30b3fb658a..9cab607e01 100644 --- a/public/language/vi/email.json +++ b/public/language/vi/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "Thông báo bài viết này được gửi cho bạn dựa tên thiết lập nhận thông báo của bạn", "test.text1": "Đây là email kiểm tra xem chức năng gửi mail trên hệ thống NodeBB của bạn có hoạt động tốt hay không.", "unsub.cta": "Nhấn vào đây để thay đổi cài đặt.", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "Xin cảm ơn!" } \ No newline at end of file diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 5e45cc0ee1..a833820d5c 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -30,6 +30,7 @@ "password-too-long": "Mật khẩu quá dài", "user-banned": "Tài khoản bị ban", "user-banned-reason": "Xin lỗi, tài khoản này đã bị khóa (Lí do: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "Rất tiếc, bạn phải chờ %1 giây để đăng bài viết đầu tiên.", "blacklisted-ip": "Rất tiếc, địa chỉ IP của bạn đã bị cấm khỏi cộng đồng. Nếu bạn cảm thấy có gì không đúng, hãy liên lạc với người quản trị.", "ban-expiry-missing": "Vui lòng cung cấp ngày hết hạn của lệnh cấm", @@ -104,7 +105,7 @@ "chat-disabled": "Hệ thống chat đã bị vô hiệu hoá", "too-many-messages": "Bạn đã gửi quá nhiều tin nhắn, vui lòng đợi trong giây lát.", "invalid-chat-message": "Tin nhắn không hợp lệ", - "chat-message-too-long": "Tin nhắn quá dài", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "Bạn không được phép chỉnh sửa tin nhắn này", "cant-remove-last-user": "Bạn không thể xoá thành viên cuối cùng", "cant-delete-chat-message": "Bạn không được phép xoá tin nhắn này", diff --git a/public/language/vi/flags.json b/public/language/vi/flags.json new file mode 100644 index 0000000000..c4bfed6f23 --- /dev/null +++ b/public/language/vi/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "Update", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "Post", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index a8c41951a3..b700406deb 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3 tháng", "chat.delete_message_confirm": "Bạn có chắc chắn bạn muốn xoá tin nhắn này chứ?", "chat.add-users-to-room": "Thêm người vào phòng", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "Soạn thảo", "composer.show_preview": "Hiện Xem trước", "composer.hide_preview": "Ẩn Xem trước", diff --git a/public/language/vi/user.json b/public/language/vi/user.json index 7603788230..c369f3af56 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "Tên truy cập này đã tồn tại, vì vậy chúng tôi đã sửa đổi nó một chút. Tên truy cập của bạn giờ là %1", "password_same_as_username": "Mật khẩu của bạn trùng với tên đăng nhập, vui lòng chọn một mật khẩu khác.", "password_same_as_email": "Mật khẩu của bạn trùng với email của bạn, hãy chọn mật khẩu khác.", + "weak_password": "Weak password.", "upload_picture": "Tải lên hình ảnh", "upload_a_picture": "Tải lên một hình ảnh", "remove_uploaded_picture": "Xoá ảnh đã tải lên", diff --git a/public/language/zh-CN/admin/advanced/database.json b/public/language/zh-CN/admin/advanced/database.json index 5519709794..825469bb2d 100644 --- a/public/language/zh-CN/admin/advanced/database.json +++ b/public/language/zh-CN/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "运行秒数", "uptime-days": "运行天数", diff --git a/public/language/zh-CN/admin/extend/plugins.json b/public/language/zh-CN/admin/extend/plugins.json index 21446a0de9..3ca42b1c2e 100644 --- a/public/language/zh-CN/admin/extend/plugins.json +++ b/public/language/zh-CN/admin/extend/plugins.json @@ -1,7 +1,7 @@ { "installed": "已安装", - "active": "激活", - "inactive": "未生效", + "active": "已启用", + "inactive": "未启用", "out-of-date": "已过期", "none-found": "无插件。", "none-active": "无生效插件", diff --git a/public/language/zh-CN/admin/manage/flags.json b/public/language/zh-CN/admin/manage/flags.json deleted file mode 100644 index e7274032ab..0000000000 --- a/public/language/zh-CN/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "日举报", - "by-user": "用户举报", - "by-user-search": "根据用户名搜索被举报帖子", - "category": "版块", - "sort-by": "排序", - "sort-by.most-flags": "次数最多", - "sort-by.most-recent": "提交时间", - "search": "搜索", - "dismiss-all": "全部忽略", - "none-flagged": "没有被举报的帖子!", - "posted-in": "发表于 %1", - "read-more": "阅读更多", - "flagged-x-times": "该贴已被举报 %1 次:", - "dismiss": "忽略该举报", - "delete-post": "删除此贴", - - "alerts.confirm-delete-post": "确定删除此贴吗?" -} \ No newline at end of file diff --git a/public/language/zh-CN/admin/manage/groups.json b/public/language/zh-CN/admin/manage/groups.json index 131199f22c..4fee922f44 100644 --- a/public/language/zh-CN/admin/manage/groups.json +++ b/public/language/zh-CN/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "群组名", "description": "群组描述", + "member-count": "Member Count", "system": "系统群组", "edit": "编辑", "search-placeholder": "搜索", diff --git a/public/language/zh-CN/admin/settings/advanced.json b/public/language/zh-CN/admin/settings/advanced.json index eb9bced36e..4cc5377e39 100644 --- a/public/language/zh-CN/admin/settings/advanced.json +++ b/public/language/zh-CN/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "设置 ALLOW-FROM 来放置 NodeBB 于 iFrame 中", "headers.powered-by": "自定义由 NodeBB 发送的 \"Powered By\" 头部 ", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "要拒绝所有网站访问?在这留空或者设置成 null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "流量管理", diff --git a/public/language/zh-CN/admin/settings/general.json b/public/language/zh-CN/admin/settings/general.json index cbb324c9e7..c003b93469 100644 --- a/public/language/zh-CN/admin/settings/general.json +++ b/public/language/zh-CN/admin/settings/general.json @@ -6,7 +6,7 @@ "browser-title": "浏览器标题", "browser-title-help": "如果没有指定浏览器标题,将会使用站点标题", "title-layout": "标题布局", - "title-layout-help": "定义浏览器标题的布局,即{页面标题} | {浏览器标题}", + "title-layout-help": "定义浏览器标题的布局,即{pageTitle} | {browserTitle}", "description.placeholder": "关于您的社区的简短说明", "description": "站点描述", "keywords": "站点关键字", diff --git a/public/language/zh-CN/admin/settings/post.json b/public/language/zh-CN/admin/settings/post.json index 857f72b98e..6f77d26602 100644 --- a/public/language/zh-CN/admin/settings/post.json +++ b/public/language/zh-CN/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "未读设置", "unread.cutoff": "未读截止天数", "unread.min-track-last": "跟踪最后阅读之前的主题最小帖子", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "签名设置", "signature.disable": "禁用签名", "signature.no-links": "禁用签名中的链接", diff --git a/public/language/zh-CN/admin/settings/user.json b/public/language/zh-CN/admin/settings/user.json index 7b13c89b95..d147da2ccc 100644 --- a/public/language/zh-CN/admin/settings/user.json +++ b/public/language/zh-CN/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "最小用户名长度", "max-username-length": "最大用户名长度", "min-password-length": "最小密码长度", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "自我介绍的最大长度", "terms-of-use": "论坛使用条款 (留空即可禁用)", "user-search": "用户搜索", diff --git a/public/language/zh-CN/email.json b/public/language/zh-CN/email.json index 823d26070f..f247ee4b1f 100644 --- a/public/language/zh-CN/email.json +++ b/public/language/zh-CN/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "根据您的订阅设置,为您发送此回帖提醒。", "test.text1": "这是一封测试邮件,用来验证 NodeBB 的邮件配置是否设置正确。", "unsub.cta": "点击这里修改这些设置", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "谢谢!" } \ No newline at end of file diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index a59beaf451..89f130c7ff 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -30,6 +30,7 @@ "password-too-long": "密码太长", "user-banned": "用户已禁止", "user-banned-reason": "抱歉,此帐号已经被封禁 (原因:%1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "抱歉,您需要等待 %1 秒后,才可以发帖!", "blacklisted-ip": "对不起,您的 IP 地址已被社区禁用。如果您认为这是一个错误,请与管理员联系。", "ban-expiry-missing": "请提供此次禁言结束日期", @@ -104,7 +105,7 @@ "chat-disabled": "聊天系统已关闭", "too-many-messages": "您发送了太多消息,请稍等片刻。", "invalid-chat-message": "无效的聊天信息", - "chat-message-too-long": "聊天信息太长", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "您不能编辑这条信息", "cant-remove-last-user": "您不能移除这个用户", "cant-delete-chat-message": "您不允许删除这条消息", diff --git a/public/language/zh-CN/flags.json b/public/language/zh-CN/flags.json new file mode 100644 index 0000000000..0346594fbc --- /dev/null +++ b/public/language/zh-CN/flags.json @@ -0,0 +1,60 @@ +{ + "state": "状态", + "reporter": "举报人", + "reported-at": "举报于", + "description": "描述", + "no-flags": "啊哈!没发现任何的举报。", + "assignee": "代理人", + "update": "更新", + "updated": "已更新", + "target-purged": "被举报的内容已经被清除,不再可用。", + + "quick-filters": "快速过滤器", + "filter-active": "该列中有一个或更多激活的过滤器", + "filter-reset": "删除过滤器", + "filters": "过滤器选项", + "filter-reporterId": "举报者UID", + "filter-targetUid": "被举报者 UID", + "filter-type": "举报类型", + "filter-type-all": "所有内容", + "filter-type-post": "帖子", + "filter-state": "状态", + "filter-assignee": "代理人UID", + "filter-cid": "版块", + "filter-quick-mine": "委托给我", + "filter-cid-all": "全部版块", + "apply-filters": "应用过滤器", + + "quick-links": "快速链接", + "flagged-user": "被举报的用户", + "view-profile": "查看个人资料", + "start-new-chat": "开始新会话", + "go-to-target": "查看举报目标", + + "user-view": "查看资料", + "user-edit": "编辑资料", + + "notes": "举报备注", + "add-note": "添加备注", + "no-notes": "没有共享的备注内容。", + + "history": "举报历史", + "back": "返回举报列表", + "no-history": "没有举报历史。", + + "state-all": "所有状态", + "state-open": "新建/打开", + "state-wip": "正在处理", + "state-resolved": "已解决", + "state-rejected": "已拒绝", + "no-assignee": "未指派", + "note-added": "备注已添加", + + "modal-title": "举报不适内容", + "modal-body": "请选择或者输入您举报 %1%2 的原因以便版主进行审核。", + "modal-reason-spam": "垃圾信息", + "modal-reason-offensive": "人身攻击", + "modal-reason-custom": "举报此内容的理由……", + "modal-submit": "提交举报", + "modal-submit-success": "已举报此内容。" +} \ No newline at end of file diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index 235ec2c80b..32241beaba 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3个月", "chat.delete_message_confirm": "确认删除此消息吗?", "chat.add-users-to-room": "向此聊天室中添加成员", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "编写帮助", "composer.show_preview": "显示预览", "composer.hide_preview": "隐藏预览", diff --git a/public/language/zh-CN/user.json b/public/language/zh-CN/user.json index 1f7842a16c..8e8c90ada7 100644 --- a/public/language/zh-CN/user.json +++ b/public/language/zh-CN/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "您申请的用户名已被占用,所以我们稍作更改。您现在的用户名是 %1", "password_same_as_username": "您的密码与用户名相同,请选择另外的密码。", "password_same_as_email": "您的密码与邮箱相同,请选择另外的密码。", + "weak_password": "Weak password.", "upload_picture": "上传头像", "upload_a_picture": "上传头像", "remove_uploaded_picture": "删除已上传的头像", diff --git a/public/language/zh-TW/admin/advanced/database.json b/public/language/zh-TW/admin/advanced/database.json index 860a040b3d..66a71edae9 100644 --- a/public/language/zh-TW/admin/advanced/database.json +++ b/public/language/zh-TW/admin/advanced/database.json @@ -1,6 +1,7 @@ { "x-b": "%1 b", "x-mb": "%1 mb", + "x-gb": "%1 gb", "uptime-seconds": "正常運作秒數", "uptime-days": "正常運作天數", diff --git a/public/language/zh-TW/admin/manage/flags.json b/public/language/zh-TW/admin/manage/flags.json deleted file mode 100644 index bfc488a409..0000000000 --- a/public/language/zh-TW/admin/manage/flags.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "daily": "Daily flags", - "by-user": "Flags by user", - "by-user-search": "Search flagged posts by username", - "category": "Category", - "sort-by": "Sort By", - "sort-by.most-flags": "Most Flags", - "sort-by.most-recent": "Most Recent", - "search": "Search", - "dismiss-all": "Dismiss All", - "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", - "read-more": "Read More", - "flagged-x-times": "This post has been flagged %1 time(s):", - "dismiss": "Dismiss this Flag", - "delete-post": "Delete the Post", - - "alerts.confirm-delete-post": "Do you really want to delete this post?" -} \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/groups.json b/public/language/zh-TW/admin/manage/groups.json index b5e526aacf..c019ec9823 100644 --- a/public/language/zh-TW/admin/manage/groups.json +++ b/public/language/zh-TW/admin/manage/groups.json @@ -1,6 +1,7 @@ { "name": "Group Name", "description": "Group Description", + "member-count": "Member Count", "system": "System Group", "edit": "Edit", "search-placeholder": "Search", diff --git a/public/language/zh-TW/admin/settings/advanced.json b/public/language/zh-TW/admin/settings/advanced.json index b023528d04..05a1929cf0 100644 --- a/public/language/zh-TW/admin/settings/advanced.json +++ b/public/language/zh-TW/admin/settings/advanced.json @@ -6,7 +6,7 @@ "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-help": "To deny access to all sites, leave empty or set to null", + "headers.acao-help": "To deny access to all sites, leave empty", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "Traffic Management", diff --git a/public/language/zh-TW/admin/settings/post.json b/public/language/zh-TW/admin/settings/post.json index f293e554d9..aca8b39d64 100644 --- a/public/language/zh-TW/admin/settings/post.json +++ b/public/language/zh-TW/admin/settings/post.json @@ -29,6 +29,8 @@ "unread": "Unread Settings", "unread.cutoff": "Unread cutoff days", "unread.min-track-last": "Minimum posts in topic before tracking last read", + "recent": "Recent Settings", + "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page", "signature": "Signature Settings", "signature.disable": "Disable signatures", "signature.no-links": "Disable links in signatures", diff --git a/public/language/zh-TW/admin/settings/user.json b/public/language/zh-TW/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/zh-TW/admin/settings/user.json +++ b/public/language/zh-TW/admin/settings/user.json @@ -37,6 +37,7 @@ "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", + "min-password-strength": "Minimum Password Strength", "max-about-me-length": "Maximum About Me Length", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", diff --git a/public/language/zh-TW/email.json b/public/language/zh-TW/email.json index d34fae331b..3e95aee620 100644 --- a/public/language/zh-TW/email.json +++ b/public/language/zh-TW/email.json @@ -32,5 +32,9 @@ "notif.post.unsub.info": "本張貼通知按你的訂閱設置發送給你。", "test.text1": "這是一個測試電子郵件,用於確認你的NodeBB郵件功能是否設置正確。", "unsub.cta": "點擊此處來更改這些設置", + "banned.subject": "You have been banned from %1", + "banned.text1": "The user %1 has been banned from %2.", + "banned.text2": "This ban will last until %1.", + "banned.text3": "This is the reason why you have been banned:", "closing": "感謝!" } \ No newline at end of file diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json index 8b3fa47a81..9ac4e1f494 100644 --- a/public/language/zh-TW/error.json +++ b/public/language/zh-TW/error.json @@ -30,6 +30,7 @@ "password-too-long": "密碼太長", "user-banned": "該使用者已被停用", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", + "user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)", "user-too-new": "抱歉,發表你第一篇文章須要等待 %1 秒", "blacklisted-ip": "抱歉,你的IP位置已經被這個社群禁用了。如果你覺得這是一個失誤,請連絡管理員。", "ban-expiry-missing": "請提供這個禁用的到期時間", @@ -104,7 +105,7 @@ "chat-disabled": "聊天系統被禁止", "too-many-messages": "你已經送出過多的訊息,請稍等一下。", "invalid-chat-message": "無效的聊天訊息", - "chat-message-too-long": "聊天訊息太長", + "chat-message-too-long": "Chat messages can not be longer than %1 characters.", "cant-edit-chat-message": "你不被允許編輯這條訊息", "cant-remove-last-user": "你不能移除最後的使用者", "cant-delete-chat-message": "你不被允許刪除這條訊息", diff --git a/public/language/zh-TW/flags.json b/public/language/zh-TW/flags.json new file mode 100644 index 0000000000..05a722bae2 --- /dev/null +++ b/public/language/zh-TW/flags.json @@ -0,0 +1,60 @@ +{ + "state": "State", + "reporter": "Reporter", + "reported-at": "Reported At", + "description": "Description", + "no-flags": "Hooray! No flags found.", + "assignee": "Assignee", + "update": "更新", + "updated": "Updated", + "target-purged": "The content this flag referred to has been purged and is no longer available.", + + "quick-filters": "Quick Filters", + "filter-active": "There are one or more filters active in this list of flags", + "filter-reset": "Remove Filters", + "filters": "Filter Options", + "filter-reporterId": "Reporter UID", + "filter-targetUid": "Flagged UID", + "filter-type": "Flag Type", + "filter-type-all": "All Content", + "filter-type-post": "文章", + "filter-state": "State", + "filter-assignee": "Assignee UID", + "filter-cid": "Category", + "filter-quick-mine": "Assigned to me", + "filter-cid-all": "All categories", + "apply-filters": "Apply Filters", + + "quick-links": "Quick Links", + "flagged-user": "Flagged User", + "view-profile": "View Profile", + "start-new-chat": "Start New Chat", + "go-to-target": "View Flag Target", + + "user-view": "View Profile", + "user-edit": "Edit Profile", + + "notes": "Flag Notes", + "add-note": "Add Note", + "no-notes": "No shared notes.", + + "history": "Flag History", + "back": "Back to Flags List", + "no-history": "No flag history.", + + "state-all": "All states", + "state-open": "New/Open", + "state-wip": "Work in Progress", + "state-resolved": "Resolved", + "state-rejected": "Rejected", + "no-assignee": "Not Assigned", + "note-added": "Note Added", + + "modal-title": "Report Inappropriate Content", + "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", + "modal-reason-spam": "Spam", + "modal-reason-offensive": "Offensive", + "modal-reason-custom": "Reason for reporting this content...", + "modal-submit": "Submit Report", + "modal-submit-success": "Content has been flagged for moderation." +} \ No newline at end of file diff --git a/public/language/zh-TW/modules.json b/public/language/zh-TW/modules.json index 753ee18d2e..b3fef620d5 100644 --- a/public/language/zh-TW/modules.json +++ b/public/language/zh-TW/modules.json @@ -20,6 +20,7 @@ "chat.three_months": "3個月", "chat.delete_message_confirm": "你確定要刪除這個訊息?", "chat.add-users-to-room": "將使用者加入聊天室中", + "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?", "composer.compose": "撰寫", "composer.show_preview": "顯示預覽", "composer.hide_preview": "隱藏預覽", diff --git a/public/language/zh-TW/user.json b/public/language/zh-TW/user.json index e0010019f8..3a1874a4a4 100644 --- a/public/language/zh-TW/user.json +++ b/public/language/zh-TW/user.json @@ -60,6 +60,7 @@ "username_taken_workaround": "你想要註冊的帳號已經被使用了,所以我們將它略作改變。你現在的帳號名稱是 %1", "password_same_as_username": "你的密碼和帳號是一樣的,請選擇另一組密碼。", "password_same_as_email": "你的密碼和電子郵件是一樣的,請選擇另一組密碼。", + "weak_password": "Weak password.", "upload_picture": "上傳頭像", "upload_a_picture": "上傳一張照片", "remove_uploaded_picture": "移除上傳的圖片", diff --git a/public/less/admin/general/dashboard.less b/public/less/admin/general/dashboard.less index c8b704707b..12e542e32a 100644 --- a/public/less/admin/general/dashboard.less +++ b/public/less/admin/general/dashboard.less @@ -55,8 +55,8 @@ .box-header-font; display: block; position: absolute; - top: 20px; - left: 35px; + top: 19px; + left: 50px; list-style-type: none; padding: 0; @@ -129,11 +129,32 @@ } } - .monthly-pageviews { + .pageview-stats { width:33%; + + strong { + font-size: 22px; + } } .motd textarea { width: 100%; } + + .stats { + .formatted-number { + font-size: 22px; + } + + .stat { + text-transform: uppercase; + font-weight: 600; + font-size: 10px; + color: #999; + } + } + + .updatePageviewsGraph.active { + font-weight: bold; + } } \ No newline at end of file diff --git a/public/less/generics.less b/public/less/generics.less index 5193627445..bab162e81f 100644 --- a/public/less/generics.less +++ b/public/less/generics.less @@ -107,6 +107,18 @@ } &.avatar-lg { + width: 64px; + height: 64px; + .user-icon-style(64px, 4rem); + } + + &.avatar-xl { + width: 128px; + height: 128px; + .user-icon-style(128px, 7.5rem); + } + + &.avatar-xl { width: 128px; height: 128px; .user-icon-style(128px, 7.5rem); diff --git a/public/logo.png b/public/logo.png index 6a6c184126..d8dcba4638 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/public/src/admin/general/dashboard.js b/public/src/admin/general/dashboard.js index 8adb9498ce..71ad4e3738 100644 --- a/public/src/admin/general/dashboard.js +++ b/public/src/admin/general/dashboard.js @@ -99,20 +99,20 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator'], function (s graphData.rooms = data; var html = '
' + - '
' + data.onlineRegisteredCount + '
' + - '
[[admin/general/dashboard:active-users.users]]
' + + '' + data.onlineRegisteredCount + '' + + '
[[admin/general/dashboard:active-users.users]]
' + '
' + '
' + - '
' + data.onlineGuestCount + '
' + - '
[[admin/general/dashboard:active-users.guests]]
' + + '' + data.onlineGuestCount + '' + + '
[[admin/general/dashboard:active-users.guests]]
' + '
' + '
' + - '
' + (data.onlineRegisteredCount + data.onlineGuestCount) + '
' + - '
[[admin/general/dashboard:active-users.total]]
' + + '' + (data.onlineRegisteredCount + data.onlineGuestCount) + '' + + '
[[admin/general/dashboard:active-users.total]]
' + '
' + '
' + - '
' + data.socketCount + '
' + - '
[[admin/general/dashboard:active-users.connections]]
' + + '' + data.socketCount + '' + + '
[[admin/general/dashboard:active-users.connections]]
' + '
'; updateRegisteredGraph(data.onlineRegisteredCount, data.onlineGuestCount); @@ -303,8 +303,12 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator'], function (s until = lastMonth.getTime(); } updateTrafficGraph($(this).attr('data-units'), until); + $('[data-action="updateGraph"]').removeClass('active'); + $(this).addClass('active'); }); + socket.emit('admin.rooms.getAll', Admin.updateRoomUsage); + initiateDashboard(); callback(); }); } diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js deleted file mode 100644 index 26e9bd3f73..0000000000 --- a/public/src/admin/manage/flags.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - - -define('admin/manage/flags', [ - 'autocomplete', - 'Chart', - 'components', - 'translator', -], function (autocomplete, Chart, components, translator) { - var Flags = {}; - - Flags.init = function () { - $('.post-container .content img:not(.not-responsive)').addClass('img-responsive'); - - autocomplete.user($('#byUsername')); - - handleDismiss(); - handleDismissAll(); - handleDelete(); - handleGraphs(); - - updateFlagDetails(ajaxify.data.posts); - - components.get('posts/flags').on('click', '[component="posts/flag/update"]', updateFlag); - - // Open flag as indicated in location bar - if (window.location.hash.startsWith('#flag-pid-')) { - $(window.location.hash).collapse('toggle'); - } - }; - - function handleDismiss() { - $('.flags').on('click', '.dismiss', function () { - var btn = $(this); - var pid = btn.parents('[data-pid]').attr('data-pid'); - - socket.emit('posts.dismissFlag', pid, function (err) { - done(err, btn); - }); - }); - } - - function handleDismissAll() { - $('#dismissAll').on('click', function () { - socket.emit('posts.dismissAllFlags', function (err) { - if (err) { - return app.alertError(err.message); - } - - ajaxify.refresh(); - }); - return false; - }); - } - - function handleDelete() { - $('.flags').on('click', '.delete', function () { - var btn = $(this); - bootbox.confirm('[[admin/manage/flags:alerts.confirm-delete-post]]', function (confirm) { - if (!confirm) { - return; - } - var pid = btn.parents('[data-pid]').attr('data-pid'); - var tid = btn.parents('[data-pid]').attr('data-tid'); - socket.emit('posts.delete', { pid: pid, tid: tid }, function (err) { - done(err, btn); - }); - }); - }); - } - - function done(err, btn) { - if (err) { - return app.alertError(err.messaage); - } - btn.parents('[data-pid]').fadeOut(function () { - $(this).remove(); - if (!$('.flags [data-pid]').length) { - translator.translate('[[admin/manage/flags:none-flagged]]', function (text) { - $('.post-container').text(text); - }); - } - }); - } - - function handleGraphs() { - var dailyCanvas = document.getElementById('flags:daily'); - var dailyLabels = utils.getDaysArray().map(function (text, idx) { - return idx % 3 ? '' : text; - }); - - if (utils.isMobile()) { - Chart.defaults.global.tooltips.enabled = false; - } - var data = { - 'flags:daily': { - labels: dailyLabels, - datasets: [ - { - label: '', - backgroundColor: 'rgba(151,187,205,0.2)', - borderColor: 'rgba(151,187,205,1)', - pointBackgroundColor: 'rgba(151,187,205,1)', - pointHoverBackgroundColor: '#fff', - pointBorderColor: '#fff', - pointHoverBorderColor: 'rgba(151,187,205,1)', - data: ajaxify.data.analytics, - }, - ], - }, - }; - - dailyCanvas.width = $(dailyCanvas).parent().width(); - new Chart(dailyCanvas.getContext('2d'), { - type: 'line', - data: data['flags:daily'], - options: { - responsive: true, - animation: false, - legend: { - display: false, - }, - scales: { - yAxes: [{ - ticks: { - beginAtZero: true, - }, - }], - }, - }, - }); - } - - function updateFlagDetails(source) { - // As the flag details are returned in the API, - // update the form controls to show the correct data - - // Create reference hash for use in this method - source = source.reduce(function (memo, cur) { - memo[cur.pid] = cur.flagData; - return memo; - }, {}); - - components.get('posts/flag').each(function (idx, el) { - var pid = el.getAttribute('data-pid'); - el = $(el); - - if (source[pid]) { - for (var prop in source[pid]) { - if (source[pid].hasOwnProperty(prop)) { - el.find('[name="' + prop + '"]').val(source[pid][prop]); - } - } - } - }); - } - - function updateFlag() { - var pid = $(this).parents('[component="posts/flag"]').attr('data-pid'); - var formData = $($(this).parents('form').get(0)).serializeArray(); - - socket.emit('posts.updateFlag', { - pid: pid, - data: formData, - }, function (err) { - if (err) { - return app.alertError(err.message); - } - app.alertSuccess('[[topic:flag_manage_saved]]'); - }); - } - - return Flags; -}); diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index 604af20bbc..ae0cc9961e 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -127,15 +127,6 @@ define('admin/manage/users', ['translator'], function (translator) { socket.emit('admin.user.resetLockouts', uids, done('[[admin/manage/users:alerts.lockout-reset-success]]')); }); - $('.reset-flags').on('click', function () { - var uids = getSelectedUids(); - if (!uids.length) { - return; - } - - socket.emit('admin.user.resetFlags', uids, done('[[admin/manage/users:alerts.flag-reset-success]]')); - }); - $('.admin-user').on('click', function () { var uids = getSelectedUids(); if (!uids.length) { diff --git a/public/src/admin/modules/search.js b/public/src/admin/modules/search.js index 7694b21b3a..0df7b7b385 100644 --- a/public/src/admin/modules/search.js +++ b/public/src/admin/modules/search.js @@ -73,7 +73,7 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) { if (!selected.length) { selected = menu.find('li.result > a').first().attr('href'); } - var href = selected || config.relative_path + '/search?in=titlesposts&term=' + input.val(); + var href = selected || config.relative_path + '/search?in=titlesposts&term=' + escape(input.val()); ajaxify.go(href.replace(/^\//, '')); @@ -140,9 +140,9 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) { menu.find('.search-forum') .not('.divider') .find('a') - .attr('href', config.relative_path + '/search?in=titlesposts&term=' + value) + .attr('href', config.relative_path + '/search?in=titlesposts&term=' + escape(value)) .find('strong') - .html(value); + .text(value); } else { menu.removeClass('state-no-results state-yes-results'); } diff --git a/public/src/app.js b/public/src/app.js index 3e53305aac..81ebe4f891 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -10,7 +10,6 @@ app.cacheBuster = null; (function () { var showWelcomeMessage = !!utils.params().loggedin; - var showBannedMessage = !!utils.params().banned && app.user && app.user.uid === 0; templates.setGlobal('config', config); @@ -191,11 +190,13 @@ app.cacheBuster = null; if (!socket) { return; } + var previousRoom = app.currentRoom; + app.currentRoom = ''; socket.emit('meta.rooms.leaveCurrent', function (err) { if (err) { + app.currentRoom = previousRoom; return app.alertError(err.message); } - app.currentRoom = ''; }); }; @@ -264,11 +265,6 @@ app.cacheBuster = null; title: '[[global:welcome_back]] ' + app.user.username + '!', message: '[[global:you_have_successfully_logged_in]]', }, - banned: { - format: 'modal', - title: '[[error:user-banned]]', - message: '[[error:user-banned-reason, ' + utils.params().banned + ']]', - }, }; function showAlert(type) { @@ -301,13 +297,6 @@ app.cacheBuster = null; showAlert('login'); }); } - - if (showBannedMessage) { - showBannedMessage = false; - $(document).ready(function () { - showAlert('banned'); - }); - } }; app.openChat = function (roomId, uid) { @@ -340,6 +329,22 @@ app.cacheBuster = null; }; app.newChat = function (touid, callback) { + function createChat() { + socket.emit('modules.chats.newRoom', { touid: touid }, function (err, roomId) { + if (err) { + return app.alertError(err.message); + } + + if (!ajaxify.data.template.chats) { + app.openChat(roomId); + } else { + ajaxify.go('chats/' + roomId); + } + + callback(false, roomId); + }); + } + callback = callback || function () {}; if (!app.user.uid) { return app.alertError('[[error:not-logged-in]]'); @@ -348,19 +353,18 @@ app.cacheBuster = null; if (parseInt(touid, 10) === parseInt(app.user.uid, 10)) { return app.alertError('[[error:cant-chat-with-yourself]]'); } - - socket.emit('modules.chats.newRoom', { touid: touid }, function (err, roomId) { + socket.emit('modules.chats.isDnD', touid, function (err, isDnD) { if (err) { return app.alertError(err.message); } - - if (!ajaxify.data.template.chats) { - app.openChat(roomId); - } else { - ajaxify.go('chats/' + roomId); + if (!isDnD) { + return createChat(); } - - callback(false, roomId); + bootbox.confirm('[[modules:chat.confirm-chat-with-dnd-user]]', function (ok) { + if (ok) { + createChat(); + } + }); }); }; @@ -630,16 +634,17 @@ app.cacheBuster = null; }; app.showCookieWarning = function () { - if (!config.cookies.enabled || !navigator.cookieEnabled) { - // Skip warning if cookie consent subsystem disabled (obviously), or cookies not in use - return; - } else if (window.location.pathname.startsWith(config.relative_path + '/admin')) { - // No need to show cookie consent warning in ACP - return; - } else if (window.localStorage.getItem('cookieconsent') === '1') { - return; - } - require(['translator'], function (translator) { + require(['translator', 'storage'], function (translator, storage) { + if (!config.cookies.enabled || !navigator.cookieEnabled) { + // Skip warning if cookie consent subsystem disabled (obviously), or cookies not in use + return; + } else if (window.location.pathname.startsWith(config.relative_path + '/admin')) { + // No need to show cookie consent warning in ACP + return; + } else if (storage.getItem('cookieconsent') === '1') { + return; + } + config.cookies.message = translator.unescape(config.cookies.message); config.cookies.dismiss = translator.unescape(config.cookies.dismiss); config.cookies.link = translator.unescape(config.cookies.link); @@ -651,7 +656,7 @@ app.cacheBuster = null; var dismissEl = warningEl.find('button'); dismissEl.on('click', function () { // Save consent cookie and remove warning element - window.localStorage.setItem('cookieconsent', '1'); + storage.setItem('cookieconsent', '1'); warningEl.remove(); }); }); diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index 1ff436c7ee..ce340c2c90 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -51,6 +51,7 @@ define('forum/account/header', [ components.get('account/ban').on('click', banAccount); components.get('account/unban').on('click', unbanAccount); components.get('account/delete').on('click', deleteAccount); + components.get('account/flag').on('click', flagAccount); }; function hidePrivateLinks() { @@ -177,6 +178,15 @@ define('forum/account/header', [ }); } + function flagAccount() { + require(['flags'], function (flags) { + flags.showFlagModal({ + type: 'user', + id: ajaxify.data.uid, + }); + }); + } + function removeCover() { translator.translate('[[user:remove_cover_picture_confirm]]', function (translated) { bootbox.confirm(translated, function (confirm) { diff --git a/public/src/client/account/info.js b/public/src/client/account/info.js index f366c0a3e7..ee6648aab5 100644 --- a/public/src/client/account/info.js +++ b/public/src/client/account/info.js @@ -17,7 +17,19 @@ define('forum/account/info', ['forum/account/header', 'components'], function (h if (err) { return app.alertError(err.message); } + $('[component="account/moderation-note"]').val(''); app.alertSuccess('[[user:info.moderation-note.success]]'); + var timestamp = Date.now(); + var data = [{ + note: note, + user: app.user, + timestamp: timestamp, + timestampISO: utils.toISOString(timestamp), + }]; + app.parseAndTranslate('account/info', 'moderationNotes', { moderationNotes: data }, function (html) { + $('[component="account/moderation-note/list"]').prepend(html); + html.find('.timeago').timeago(); + }); }); }); } diff --git a/public/src/client/account/topics.js b/public/src/client/account/topics.js index 340c2e11b9..c16ffd913d 100644 --- a/public/src/client/account/topics.js +++ b/public/src/client/account/topics.js @@ -27,6 +27,7 @@ define('forum/account/topics', ['forum/account/header', 'forum/infinitescroll'], infinitescroll.loadMore('topics.loadMoreFromSet', { set: set, after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, }, function (data, done) { if (data.topics && data.topics.length) { onTopicsLoaded(data.topics, done); diff --git a/public/src/client/category.js b/public/src/client/category.js index e2dfcd9924..504f132697 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -11,7 +11,8 @@ define('forum/category', [ 'translator', 'topicSelect', 'forum/pagination', -], function (infinitescroll, share, navigator, categoryTools, sort, components, translator, topicSelect, pagination) { + 'storage', +], function (infinitescroll, share, navigator, categoryTools, sort, components, translator, topicSelect, pagination, storage) { var Category = {}; $(window).on('action:ajaxify.start', function (ev, data) { @@ -41,18 +42,14 @@ define('forum/category', [ sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug); - if (!config.usePagination) { - navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback); - } - enableInfiniteLoadingOrPagination(); $('[component="category"]').on('click', '[component="topic/header"]', function () { var clickedIndex = $(this).parents('[data-index]').attr('data-index'); $('[component="category/topic"]').each(function (index, el) { if ($(el).offset().top - $(window).scrollTop() > 0) { - localStorage.setItem('category:' + cid + ':bookmark', $(el).attr('data-index')); - localStorage.setItem('category:' + cid + ':bookmark:clicked', clickedIndex); + storage.setItem('category:' + cid + ':bookmark', $(el).attr('data-index')); + storage.setItem('category:' + cid + ':bookmark:clicked', clickedIndex); return false; } }); @@ -118,8 +115,8 @@ define('forum/category', [ $(window).on('action:ajaxify.contentLoaded', function () { if (ajaxify.data.template.category && ajaxify.data.cid) { - var bookmarkIndex = localStorage.getItem('category:' + ajaxify.data.cid + ':bookmark'); - var clickedIndex = localStorage.getItem('category:' + ajaxify.data.cid + ':bookmark:clicked'); + var bookmarkIndex = storage.getItem('category:' + ajaxify.data.cid + ':bookmark'); + var clickedIndex = storage.getItem('category:' + ajaxify.data.cid + ':bookmark:clicked'); bookmarkIndex = Math.max(0, parseInt(bookmarkIndex, 10) || 0); clickedIndex = Math.max(0, parseInt(clickedIndex, 10) || 0); @@ -187,6 +184,7 @@ define('forum/category', [ function enableInfiniteLoadingOrPagination() { if (!config.usePagination) { + navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback); infinitescroll.init($('[component="category"]'), Category.loadMoreTopics); } else { navigator.disable(); diff --git a/public/src/client/chats.js b/public/src/client/chats.js index e13c2131fd..c7cb1921ae 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -83,6 +83,7 @@ define('forum/chats', [ Chats.addRenameHandler(ajaxify.data.roomId, $('[component="chat/room/name"]')); Chats.addScrollHandler(ajaxify.data.roomId, ajaxify.data.uid, $('.chat-content')); + Chats.addCharactersLeftHandler(components.get('chat/input')); }; Chats.addScrollHandler = function (roomId, uid, el) { @@ -123,6 +124,12 @@ define('forum/chats', [ }); }; + Chats.addCharactersLeftHandler = function (element) { + element.on('keyup', function () { + $('[component="chat/message/length"]').text(element.val().length); + }); + }; + Chats.addEditDeleteHandler = function (element, roomId) { element.on('click', '[data-action="edit"]', function () { var messageId = $(this).parents('[data-mid]').attr('data-mid'); @@ -297,11 +304,17 @@ define('forum/chats', [ if (err) { return app.alertError(err.message); } - if (parseInt(roomId, 10) === ajaxify.data.roomId) { + if (parseInt(roomId, 10) === parseInt(ajaxify.data.roomId, 10)) { ajaxify.go('user/' + ajaxify.data.userslug + '/chats'); } else { el.remove(); } + require(['chat'], function (chat) { + var modal = chat.getModal(roomId); + if (modal.length) { + chat.close(modal); + } + }); }); }; diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index c8300c485c..27f19ca33c 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -9,7 +9,7 @@ define('forum/chats/messages', ['components', 'sounds', 'translator'], function var mid = inputEl.attr('data-mid'); if (msg.length > ajaxify.data.maximumChatMessageLength) { - return app.alertError('[[error:chat-message-too-long]]'); + return app.alertError('[[error:chat-message-too-long,' + ajaxify.data.maximumChatMessageLength + ']]'); } if (!msg.length) { @@ -35,7 +35,14 @@ define('forum/chats/messages', ['components', 'sounds', 'translator'], function if (err.message === '[[error:email-not-confirmed-chat]]') { return app.showEmailConfirmWarning(err); } - return app.alertError(err.message); + + return app.alert({ + alert_id: 'chat_spam_error', + title: '[[global:alert.error]]', + message: err.message, + type: 'danger', + timeout: 10000, + }); } sounds.play('chat-outgoing'); @@ -57,8 +64,10 @@ define('forum/chats/messages', ['components', 'sounds', 'translator'], function messages.appendChatMessage = function (chatContentEl, data) { var lastSpeaker = parseInt(chatContentEl.find('.chat-message').last().attr('data-uid'), 10); + var lasttimestamp = parseInt(chatContentEl.find('.chat-message').last().attr('data-timestamp'), 10); if (!Array.isArray(data)) { - data.newSet = lastSpeaker !== data.fromuid; + data.newSet = lastSpeaker !== parseInt(data.fromuid, 10) || + parseInt(data.timestamp, 10) > parseInt(lasttimestamp, 10) + (1000 * 60 * 3); } messages.parseMessage(data, function (html) { diff --git a/public/src/client/flags/detail.js b/public/src/client/flags/detail.js new file mode 100644 index 0000000000..d01b908fc5 --- /dev/null +++ b/public/src/client/flags/detail.js @@ -0,0 +1,75 @@ +'use strict'; + +/* globals define */ + +define('forum/flags/detail', ['forum/flags/list', 'components', 'translator'], function (FlagsList, components, translator) { + var Flags = {}; + + Flags.init = function () { + // Update attributes + $('#state').val(ajaxify.data.state).removeAttr('disabled'); + $('#assignee').val(ajaxify.data.assignee).removeAttr('disabled'); + + $('[data-action]').on('click', function () { + var action = this.getAttribute('data-action'); + + switch (action) { + case 'update': + socket.emit('flags.update', { + flagId: ajaxify.data.flagId, + data: $('#attributes').serializeArray(), + }, function (err, history) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('[[flags:updated]]'); + Flags.reloadHistory(history); + }); + break; + + case 'appendNote': + socket.emit('flags.appendNote', { + flagId: ajaxify.data.flagId, + note: document.getElementById('note').value, + }, function (err, payload) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('[[flags:note-added]]'); + Flags.reloadNotes(payload.notes); + Flags.reloadHistory(payload.history); + }); + break; + } + }); + + FlagsList.enableFilterForm(); + }; + + Flags.reloadNotes = function (notes) { + templates.parse('flags/detail', 'notes', { + notes: notes, + }, function (html) { + var wrapperEl = components.get('flag/notes'); + wrapperEl.empty(); + wrapperEl.html(html); + wrapperEl.find('span.timeago').timeago(); + document.getElementById('note').value = ''; + }); + }; + + Flags.reloadHistory = function (history) { + templates.parse('flags/detail', 'history', { + history: history, + }, function (html) { + translator.translate(html, function (translated) { + var wrapperEl = components.get('flag/history'); + wrapperEl.empty(); + wrapperEl.html(translated); + wrapperEl.find('span.timeago').timeago(); + }); + }); + }; + + return Flags; +}); diff --git a/public/src/client/flags/list.js b/public/src/client/flags/list.js new file mode 100644 index 0000000000..0806195ac4 --- /dev/null +++ b/public/src/client/flags/list.js @@ -0,0 +1,87 @@ +'use strict'; + +/* globals define */ + +define('forum/flags/list', ['components', 'Chart'], function (components, Chart) { + var Flags = {}; + + Flags.init = function () { + Flags.enableFilterForm(); + Flags.enableChatButtons(); + Flags.handleGraphs(); + }; + + Flags.enableFilterForm = function () { + var filtersEl = components.get('flags/filters'); + + // Parse ajaxify data to set form values to reflect current filters + for (var filter in ajaxify.data.filters) { + if (ajaxify.data.filters.hasOwnProperty(filter)) { + filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]); + } + } + + filtersEl.find('button').on('click', function () { + var payload = filtersEl.serializeArray().filter(function (item) { + return !!item.value; + }); + ajaxify.go('flags?' + $.param(payload)); + }); + }; + + Flags.enableChatButtons = function () { + $('[data-chat]').on('click', function () { + app.newChat(this.getAttribute('data-chat')); + }); + }; + + Flags.handleGraphs = function () { + var dailyCanvas = document.getElementById('flags:daily'); + var dailyLabels = utils.getDaysArray().map(function (text, idx) { + return idx % 3 ? '' : text; + }); + + if (utils.isMobile()) { + Chart.defaults.global.tooltips.enabled = false; + } + var data = { + 'flags:daily': { + labels: dailyLabels, + datasets: [ + { + label: '', + backgroundColor: 'rgba(151,187,205,0.2)', + borderColor: 'rgba(151,187,205,1)', + pointBackgroundColor: 'rgba(151,187,205,1)', + pointHoverBackgroundColor: '#fff', + pointBorderColor: '#fff', + pointHoverBorderColor: 'rgba(151,187,205,1)', + data: ajaxify.data.analytics, + }, + ], + }, + }; + + dailyCanvas.width = $(dailyCanvas).parent().width(); + new Chart(dailyCanvas.getContext('2d'), { + type: 'line', + data: data['flags:daily'], + options: { + responsive: true, + animation: false, + legend: { + display: false, + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true, + }, + }], + }, + }, + }); + }; + + return Flags; +}); diff --git a/public/src/client/footer.js b/public/src/client/footer.js index eb921f36c6..25444f0912 100644 --- a/public/src/client/footer.js +++ b/public/src/client/footer.js @@ -6,14 +6,8 @@ define('forum/footer', ['notifications', 'chat', 'components', 'translator'], fu Chat.prepareDOM(); translator.prepareDOM(); - function updateUnreadTopicCount(count) { - $('#unread-count i') - .toggleClass('unread-count', count > 0) - .attr('data-content', count > 99 ? '99+' : count); - } - - function updateUnreadNewTopicCount(count) { - $('#unread-new-count i') + function updateUnreadTopicCount(url, count) { + $('a[href="' + config.relative_path + url + '"] i') .toggleClass('unread-count', count > 0) .attr('data-content', count > 99 ? '99+' : count); } @@ -67,14 +61,20 @@ define('forum/footer', ['notifications', 'chat', 'components', 'translator'], fu return app.alert(err.message); } - updateUnreadTopicCount(data.unreadTopicCount); - updateUnreadNewTopicCount(data.unreadNewTopicCount); + updateUnreadCounters(data); + updateUnreadChatCount(data.unreadChatCount); Notifications.updateNotifCount(data.unreadNotificationCount); }); } - socket.on('event:unread.updateCount', updateUnreadTopicCount); + function updateUnreadCounters(data) { + updateUnreadTopicCount('/unread', data.unreadTopicCount); + updateUnreadTopicCount('/unread/new', data.unreadNewTopicCount); + updateUnreadTopicCount('/unread/watched', data.unreadWatchedTopicCount); + } + + socket.on('event:unread.updateCount', updateUnreadCounters); socket.on('event:unread.updateChatCount', updateUnreadChatCount); initUnreadTopics(); diff --git a/public/src/client/notifications.js b/public/src/client/notifications.js index fb61a53063..04213499e3 100644 --- a/public/src/client/notifications.js +++ b/public/src/client/notifications.js @@ -1,7 +1,7 @@ 'use strict'; -define('forum/notifications', ['components', 'notifications', 'forum/infinitescroll'], function (components, notifs, infinitescroll) { +define('forum/notifications', ['components', 'notifications'], function (components, notifs) { var Notifications = {}; Notifications.init = function () { @@ -35,32 +35,7 @@ define('forum/notifications', ['components', 'notifications', 'forum/infinitescr notifs.updateNotifCount(0); }); }); - - infinitescroll.init(loadMoreNotifications); }; - function loadMoreNotifications(direction) { - if (direction < 0) { - return; - } - var notifList = $('.notifications-list'); - infinitescroll.loadMore('notifications.loadMore', { - after: notifList.attr('data-nextstart'), - }, function (data, done) { - if (!data) { - return done(); - } - notifList.attr('data-nextstart', data.nextStart); - if (!data.notifications || !data.notifications.length) { - return done(); - } - app.parseAndTranslate('notifications', 'notifications', { notifications: data.notifications }, function (html) { - notifList.append(html); - html.find('.timeago').timeago(); - done(); - }); - }); - } - return Notifications; }); diff --git a/public/src/client/recent.js b/public/src/client/recent.js index 981baabd12..71b91beced 100644 --- a/public/src/client/recent.js +++ b/public/src/client/recent.js @@ -132,6 +132,7 @@ define('forum/recent', ['forum/infinitescroll', 'components'], function (infinit infinitescroll.loadMore('topics.loadMoreRecentTopics', { after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, cid: utils.params().cid, filter: ajaxify.data.selectedFilter.filter, set: $('[component="category"]').attr('data-set') ? $('[component="category"]').attr('data-set') : 'topics:recent', @@ -157,7 +158,7 @@ define('forum/recent', ['forum/infinitescroll', 'components'], function (infinit app.parseAndTranslate(templateName, 'topics', { topics: topics, showSelect: showSelect }, function (html) { $('#category-no-topics').remove(); - $('[component="category"]').append(html); + html.insertAfter($('[component="category/topic"]').last()); html.find('.timeago').timeago(); app.createUserTooltips(); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); diff --git a/public/src/client/search.js b/public/src/client/search.js index 4f65935913..e6691e626e 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -1,7 +1,7 @@ 'use strict'; -define('forum/search', ['search', 'autocomplete'], function (searchModule, autocomplete) { +define('forum/search', ['search', 'autocomplete', 'storage'], function (searchModule, autocomplete, storage) { var Search = {}; Search.init = function () { @@ -104,8 +104,8 @@ define('forum/search', ['search', 'autocomplete'], function (searchModule, autoc $('#post-time-filter').val(formData.timeFilter); } - if (formData.sortBy) { - $('#post-sort-by').val(formData.sortBy); + if (formData.sortBy || ajaxify.data.searchDefaultSortBy) { + $('#post-sort-by').val(formData.sortBy || ajaxify.data.searchDefaultSortBy); $('#post-sort-direction').val(formData.sortDirection); } @@ -147,13 +147,13 @@ define('forum/search', ['search', 'autocomplete'], function (searchModule, autoc function handleSavePreferences() { $('#save-preferences').on('click', function () { - localStorage.setItem('search-preferences', JSON.stringify(getSearchData())); + storage.setItem('search-preferences', JSON.stringify(getSearchData())); app.alertSuccess('[[search:search-preferences-saved]]'); return false; }); $('#clear-preferences').on('click', function () { - localStorage.removeItem('search-preferences'); + storage.removeItem('search-preferences'); var query = $('#search-input').val(); $('#advanced-search')[0].reset(); $('#search-input').val(query); diff --git a/public/src/client/tag.js b/public/src/client/tag.js index 2b74c17193..e07126cb2e 100644 --- a/public/src/client/tag.js +++ b/public/src/client/tag.js @@ -27,6 +27,7 @@ define('forum/tag', ['forum/recent', 'forum/infinitescroll'], function (recent, infinitescroll.loadMore('topics.loadMoreFromSet', { set: 'tag:' + ajaxify.data.tag + ':topics', after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, }, function (data, done) { if (data.topics && data.topics.length) { recent.onTopicsLoaded('tag', data.topics, false, done); diff --git a/public/src/client/topic.js b/public/src/client/topic.js index d620da1e70..c88af60237 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -12,7 +12,8 @@ define('forum/topic', [ 'navigator', 'sort', 'components', -], function (infinitescroll, threadTools, postTools, events, posts, images, replies, navigator, sort, components) { + 'storage', +], function (infinitescroll, threadTools, postTools, events, posts, images, replies, navigator, sort, components, storage) { var Topic = {}; var currentUrl = ''; @@ -142,7 +143,7 @@ define('forum/topic', [ function handleBookmark(tid) { // use the user's bookmark data if available, fallback to local if available - var bookmark = ajaxify.data.bookmark || localStorage.getItem('topic:' + tid + ':bookmark'); + var bookmark = ajaxify.data.bookmark || storage.getItem('topic:' + tid + ':bookmark'); var postIndex = getPostIndex(); if (postIndex && window.location.search.indexOf('page=') === -1) { @@ -150,24 +151,21 @@ define('forum/topic', [ return navigator.scrollToPostIndex(postIndex, true, 0); } } else if (bookmark && (!config.usePagination || (config.usePagination && ajaxify.data.pagination.currentPage === 1)) && ajaxify.data.postcount > ajaxify.data.bookmarkThreshold) { - navigator.update(0); app.alert({ alert_id: 'bookmark', message: '[[topic:bookmark_instructions]]', timeout: 0, type: 'info', clickfn: function () { - navigator.scrollToPost(parseInt(bookmark - 1, 10), true); + navigator.scrollToIndex(parseInt(bookmark - 1, 10), true); }, closefn: function () { - localStorage.removeItem('topic:' + tid + ':bookmark'); + storage.removeItem('topic:' + tid + ':bookmark'); }, }); setTimeout(function () { app.removeAlert('bookmark'); }, 10000); - } else { - navigator.update(0); } } @@ -204,7 +202,7 @@ define('forum/topic', [ var toPost = $('[component="post"][data-pid="' + toPid + '"]'); if (toPost.length) { e.preventDefault(); - navigator.scrollToPost(toPost.attr('data-index'), true); + navigator.scrollToIndex(toPost.attr('data-index'), true); return false; } }); @@ -273,7 +271,7 @@ define('forum/topic', [ function updateUserBookmark(index) { var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark'; - var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey); + var currentBookmark = ajaxify.data.bookmark || storage.getItem(bookmarkKey); if (ajaxify.data.postcount > ajaxify.data.bookmarkThreshold && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) { if (app.user.uid) { @@ -287,7 +285,7 @@ define('forum/topic', [ ajaxify.data.bookmark = index; }); } else { - localStorage.setItem(bookmarkKey, index); + storage.setItem(bookmarkKey, index); } } diff --git a/public/src/client/topic/images.js b/public/src/client/topic/images.js index 52f0671a78..65ad386371 100644 --- a/public/src/client/topic/images.js +++ b/public/src/client/topic/images.js @@ -28,6 +28,10 @@ define('forum/topic/images', [ clearTimeout(Images._imageLoaderTimeout); } + if (!config.delayImageLoading) { + return; + } + Images._imageLoaderTimeout = setTimeout(function () { /* If threshold is defined, images loaded above this threshold will modify diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index e012b20186..8bbda4d79c 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -116,10 +116,11 @@ define('forum/topic/postTools', [ postContainer.on('click', '[component="post/flag"]', function () { var pid = getData($(this), 'data-pid'); - var username = getData($(this), 'data-username'); - var userslug = getData($(this), 'data-userslug'); - require(['forum/topic/flag'], function (flag) { - flag.showFlagModal(pid, username, userslug); + require(['flags'], function (flags) { + flags.showFlagModal({ + type: 'post', + id: pid, + }); }); }); @@ -294,7 +295,7 @@ define('forum/topic/postTools', [ socket.emit(method, { pid: pid, - room_id: app.currentRoom, + room_id: 'topic_' + ajaxify.data.tid, }, function (err) { if (err) { app.alertError(err.message); diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 72f580b582..fb9074b3be 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -227,6 +227,7 @@ define('forum/topic/posts', [ infinitescroll.loadMore('topics.loadMore', { tid: tid, after: after, + count: config.postsPerPage, direction: direction, topicPostSort: config.topicPostSort, }, function (data, done) { diff --git a/public/src/client/topic/replies.js b/public/src/client/topic/replies.js index 56b5981843..7d9b9474bc 100644 --- a/public/src/client/topic/replies.js +++ b/public/src/client/topic/replies.js @@ -19,9 +19,9 @@ define('forum/topic/replies', ['navigator', 'components', 'forum/topic/posts'], function onRepliesClicked(button) { var post = button.closest('[data-pid]'); var pid = post.data('pid'); - var open = button.children('[component="post/replies/open"]'); - var loading = button.children('[component="post/replies/loading"]'); - var close = button.children('[component="post/replies/close"]'); + var open = button.find('[component="post/replies/open"]'); + var loading = button.find('[component="post/replies/loading"]'); + var close = button.find('[component="post/replies/close"]'); if (open.is(':not(.hidden)') && loading.is('.hidden')) { open.addClass('hidden'); @@ -84,10 +84,16 @@ define('forum/topic/replies', ['navigator', 'components', 'forum/topic/posts'], function incrementCount(post, inc) { var replyCount = $('[component="post"][data-pid="' + post.toPid + '"]').find('[component="post/reply-count"]').first(); var countEl = replyCount.find('[component="post/reply-count/text"]'); + var avatars = replyCount.find('[component="post/reply-count/avatars"]'); var count = Math.max(0, parseInt(countEl.attr('data-replies'), 10) + inc); + var timestamp = replyCount.find('.timeago').attr('title', post.timestampISO); + countEl.attr('data-replies', count); replyCount.toggleClass('hidden', !count); countEl.translateText('[[topic:replies_to_this_post, ' + count + ']]'); + avatars.addClass('hasMore'); + + timestamp.data('timeago', null).timeago(); } return Replies; diff --git a/public/src/client/topic/votes.js b/public/src/client/topic/votes.js index d152d9f4a0..99ca469fe3 100644 --- a/public/src/client/topic/votes.js +++ b/public/src/client/topic/votes.js @@ -67,7 +67,7 @@ define('forum/topic/votes', ['components', 'translator'], function (components, socket.emit(currentState ? 'posts.unvote' : method, { pid: post.attr('data-pid'), - room_id: app.currentRoom, + room_id: 'topic_' + ajaxify.data.tid, }, function (err) { if (err) { if (err.message === 'self-vote') { diff --git a/public/src/client/unread.js b/public/src/client/unread.js index f5b1dd73ed..01ef9cff52 100644 --- a/public/src/client/unread.js +++ b/public/src/client/unread.js @@ -92,6 +92,7 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll', ' var cid = params.cid; infinitescroll.loadMore('topics.loadMoreUnreadTopics', { after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, cid: cid, filter: ajaxify.data.selectedFilter.filter, }, function (data, done) { diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 832afc0f49..d245e5ce23 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -69,7 +69,7 @@ define('chat', [ sounds.play('chat-incoming', 'chat.incoming:' + data.message.mid); taskbar.push('chat', modal.attr('UUID'), { - title: username, + title: data.roomName || username, touid: data.message.fromUser.uid, roomId: data.roomId, }); @@ -103,7 +103,10 @@ define('chat', [ }); socket.on('event:chats.roomRename', function (data) { - module.getModal(data.roomId).find('[component="chat/room/name"]').val($('
').html(data.newName).text()); + var newTitle = $('
').html(data.newName).text(); + var modal = module.getModal(data.roomId); + modal.find('[component="chat/room/name"]').val(newTitle); + taskbar.updateTitle('chat', modal.attr('UUID'), newTitle); }); ChatsMessages.onChatMessageEdit(); @@ -263,6 +266,8 @@ define('chat', [ Chats.addScrollHandler(chatModal.attr('data-roomid'), data.uid, chatModal.find('.chat-content')); + Chats.addCharactersLeftHandler(chatModal.find('[component="chat/input"]')); + taskbar.push('chat', chatModal.attr('UUID'), { title: data.roomName || (data.users.length ? data.users[0].username : ''), roomId: data.roomId, diff --git a/public/src/client/topic/flag.js b/public/src/modules/flags.js similarity index 62% rename from public/src/client/topic/flag.js rename to public/src/modules/flags.js index 1d1c68a8c3..f95953c65e 100644 --- a/public/src/client/topic/flag.js +++ b/public/src/modules/flags.js @@ -1,17 +1,13 @@ 'use strict'; -define('forum/topic/flag', [], function () { +define('flags', [], function () { var Flag = {}; var flagModal; var flagCommit; - Flag.showFlagModal = function (pid, username, userslug) { - parseModal({ - pid: pid, - username: username, - userslug: userslug, - }, function (html) { + Flag.showFlagModal = function (data) { + parseModal(data, function (html) { flagModal = $(html); flagModal.on('hidden.bs.modal', function () { @@ -21,11 +17,11 @@ define('forum/topic/flag', [], function () { flagCommit = flagModal.find('#flag-post-commit'); flagModal.on('click', '.flag-reason', function () { - flagPost(pid, $(this).text()); + createFlag(data.type, data.id, $(this).text()); }); flagCommit.on('click', function () { - flagPost(pid, flagModal.find('#flag-reason-custom').val()); + createFlag(data.type, data.id, flagModal.find('#flag-reason-custom').val()); }); flagModal.modal('show'); @@ -35,24 +31,24 @@ define('forum/topic/flag', [], function () { }; function parseModal(tplData, callback) { - templates.parse('partials/modals/flag_post_modal', tplData, function (html) { + templates.parse('partials/modals/flag_modal', tplData, function (html) { require(['translator'], function (translator) { translator.translate(html, callback); }); }); } - function flagPost(pid, reason) { - if (!pid || !reason) { + function createFlag(type, id, reason) { + if (!type || !id || !reason) { return; } - socket.emit('posts.flag', { pid: pid, reason: reason }, function (err) { + socket.emit('flags.create', { type: type, id: id, reason: reason }, function (err) { if (err) { return app.alertError(err.message); } flagModal.modal('hide'); - app.alertSuccess('[[topic:flag_success]]'); + app.alertSuccess('[[flags:modal-submit-success]]'); }); } diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 3f02a86758..e1cf620607 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -176,7 +176,8 @@ }).join(''); }; - helpers.localeToHTML = function (locale) { + helpers.localeToHTML = function (locale, fallback) { + locale = locale || fallback || 'en-GB'; return locale.replace('_', '-'); }; diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index 68c0359755..9ca7e94fa9 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -1,7 +1,5 @@ - 'use strict'; - define('navigator', ['forum/pagination', 'components'], function (pagination, components) { var navigator = {}; var index = 1; @@ -55,6 +53,7 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co }); navigator.setCount(count); + navigator.update(0); }; function generateUrl(index) { @@ -183,7 +182,7 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co navigator.scrollTop = function (index) { if ($(navigator.selector + '[data-index="' + index + '"]').length) { - navigator.scrollToPost(index, true); + navigator.scrollToIndex(index, true); } else { ajaxify.go(generateUrl()); } @@ -193,44 +192,67 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co if (parseInt(index, 10) < 0) { return; } + if ($(navigator.selector + '[data-index="' + index + '"]').length) { - navigator.scrollToPost(index, true); + navigator.scrollToIndex(index, true); } else { index = parseInt(index, 10) + 1; ajaxify.go(generateUrl(index)); } }; - navigator.scrollToPost = function (postIndex, highlight, duration) { - if (!utils.isNumber(postIndex) || !components.get('topic').length) { + navigator.scrollToPost = function (index, highlight, duration) { + console.log('[navigator.scrollToPost] deprecated please use, navigator.scrollToIndex'); + navigator.scrollToIndex(index, highlight, duration); + }; + + navigator.scrollToIndex = function (index, highlight, duration) { + var inTopic = !!components.get('topic').length; + var inCategory = !!components.get('category').length; + + if (!utils.isNumber(index) || (!inTopic && !inCategory)) { return; } duration = duration !== undefined ? duration : 400; navigator.scrollActive = true; - if (components.get('post/anchor', postIndex).length) { - return navigator.scrollToPostIndex(postIndex, highlight, duration); + // if in topic and item already on page + if (inTopic && components.get('post/anchor', index).length) { + return navigator.scrollToPostIndex(index, highlight, duration); + } + + // if in category and item alreay on page + if (inCategory && $('[component="category/topic"][data-index="' + index + '"]').length) { + return navigator.scrollToTopicIndex(index, highlight, duration); + } + + if (!config.usePagination) { + navigator.scrollActive = false; + index = parseInt(index, 10) + 1; + ajaxify.go(generateUrl(index)); + return; } - if (config.usePagination) { - var index = postIndex; + var scrollMethod = inTopic ? navigator.scrollToPostIndex : navigator.scrollToTopicIndex; + if (inTopic) { if (config.topicPostSort === 'most_votes' || config.topicPostSort === 'newest_to_oldest') { index = ajaxify.data.postcount - index; } - var page = Math.max(1, Math.ceil(index / config.postsPerPage)); - - if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) { - pagination.loadPage(page, function () { - navigator.scrollToPostIndex(postIndex, highlight, duration); - }); - } else { - navigator.scrollToPostIndex(postIndex, highlight, duration); + } else if (inCategory) { + if (config.categoryTopicSort === 'most_posts' || config.categoryTopicSort === 'oldest_to_newest') { + index = ajaxify.data.ajaxify.data.topic_count - index; } + } + + var page = Math.max(1, Math.ceil(index / config.postsPerPage)); + + if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) { + pagination.loadPage(page, function () { + scrollMethod(index, highlight, duration); + }); } else { - navigator.scrollActive = false; - postIndex = parseInt(postIndex, 10) + 1; - ajaxify.go(generateUrl(postIndex)); + scrollMethod(index, highlight, duration); } }; @@ -239,6 +261,11 @@ define('navigator', ['forum/pagination', 'components'], function (pagination, co navigator.scrollToElement(scrollTo, highlight, duration); }; + navigator.scrollToTopicIndex = function (topicIndex, highlight, duration) { + var scrollTo = $('[component="category/topic"][data-index="' + topicIndex + '"]'); + navigator.scrollToElement(scrollTo, highlight, duration); + }; + navigator.scrollToElement = function (scrollTo, highlight, duration) { if (!scrollTo.length) { navigator.scrollActive = false; diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 1401bf8619..5b77ab7572 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -1,7 +1,7 @@ 'use strict'; -define('search', ['navigator', 'translator'], function (nav, translator) { +define('search', ['navigator', 'translator', 'storage'], function (nav, translator, storage) { var Search = { current: {}, }; @@ -79,7 +79,7 @@ define('search', ['navigator', 'translator'], function (nav, translator) { Search.getSearchPreferences = function () { try { - return JSON.parse(localStorage.getItem('search-preferences') || '{}'); + return JSON.parse(storage.getItem('search-preferences') || '{}'); } catch (e) { return {}; } diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js index 38bbaec9cb..9a5f560447 100644 --- a/public/src/modules/sounds.js +++ b/public/src/modules/sounds.js @@ -1,7 +1,7 @@ 'use strict'; -define('sounds', function () { +define('sounds', ['storage'], function (storage) { var Sounds = {}; var fileMap; @@ -67,13 +67,13 @@ define('sounds', function () { if (id) { var item = 'sounds.handled:' + id; - if (localStorage.getItem(item)) { + if (storage.getItem(item)) { return; } - localStorage.setItem(item, true); + storage.setItem(item, true); setTimeout(function () { - localStorage.removeItem(item); + storage.removeItem(item); }, 5000); } diff --git a/public/src/modules/storage.js b/public/src/modules/storage.js new file mode 100644 index 0000000000..bccaabcf6b --- /dev/null +++ b/public/src/modules/storage.js @@ -0,0 +1,84 @@ +'use strict'; + +/** + * Checks localStorage and provides a fallback if it doesn't exist or is disabled + */ +define('storage', function () { + function Storage() { + this._store = {}; + this._keys = []; + } + Storage.prototype.isMock = true; + Storage.prototype.setItem = function (key, val) { + key = String(key); + if (this._keys.indexOf(key) === -1) { + this._keys.push(key); + } + this._store[key] = val; + }; + Storage.prototype.getItem = function (key) { + key = String(key); + if (this._keys.indexOf(key) === -1) { + return null; + } + + return this._store[key]; + }; + Storage.prototype.removeItem = function (key) { + key = String(key); + this._keys = this._keys.filter(function (x) { + return x !== key; + }); + this._store[key] = null; + }; + Storage.prototype.clear = function () { + this._keys = []; + this._store = {}; + }; + Storage.prototype.key = function (n) { + n = parseInt(n, 10) || 0; + return this._keys[n]; + }; + if (Object.defineProperty) { + Object.defineProperty(Storage.prototype, 'length', { + get: function () { + return this._keys.length; + }, + }); + } + + var storage; + var item = Date.now().toString(); + + try { + storage = window.localStorage; + storage.setItem(item, item); + if (storage.getItem(item) !== item) { + throw Error('localStorage behaved unexpectedly'); + } + storage.removeItem(item); + + return storage; + } catch (e) { + console.warn(e); + console.warn('localStorage failed, falling back on sessionStorage'); + + // see if sessionStorage works, and if so, return that + try { + storage = window.sessionStorage; + storage.setItem(item, item); + if (storage.getItem(item) !== item) { + throw Error('sessionStorage behaved unexpectedly'); + } + storage.removeItem(item); + + return storage; + } catch (e) { + console.warn(e); + console.warn('sessionStorage failed, falling back on memory storage'); + + // return an object implementing mock methods + return new Storage(); + } + } +}); diff --git a/public/src/modules/taskbar.js b/public/src/modules/taskbar.js index 3897fbe6a1..49c57c5aa1 100644 --- a/public/src/modules/taskbar.js +++ b/public/src/modules/taskbar.js @@ -115,7 +115,7 @@ define('taskbar', function () { .html('' + (data.options.icon ? ' ' : '') + (data.options.image ? ' ' : '') + - '' + title + '' + + '' + title + '' + '') .attr({ 'data-module': data.module, @@ -136,5 +136,9 @@ define('taskbar', function () { $(window).trigger('action:taskbar.pushed', data); } + taskbar.updateTitle = function (module, uuid, newTitle) { + taskbar.tasklist.find('[data-module="' + module + '"][data-uuid="' + uuid + '"] [component="taskbar/title"]').text(newTitle); + }; + return taskbar; }); diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index 2e9dcafb9c..afd64317ef 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -292,7 +292,7 @@ return Promise.all(argsToTranslate).then(function (translatedArgs) { var out = translated; translatedArgs.forEach(function (arg, i) { - var escaped = arg.replace(/%/g, '%').replace(/\\,/g, ','); + var escaped = arg.replace(/%(?=\d)/g, '%').replace(/\\,/g, ','); out = out.replace(new RegExp('%' + (i + 1), 'g'), escaped); }); return out; @@ -415,7 +415,7 @@ * @returns {string} */ Translator.escape = function escape(text) { - return typeof text === 'string' ? text.replace(/\[/g, '[').replace(/\]/g, ']') : text; + return typeof text === 'string' ? text.replace(/\[\[/g, '[[').replace(/\]\]/g, ']]') : text; }; /** diff --git a/public/src/sockets.js b/public/src/sockets.js index 6b22ce9ac9..6b11f49e4f 100644 --- a/public/src/sockets.js +++ b/public/src/sockets.js @@ -121,7 +121,16 @@ app.isConnected = false; app.isConnected = false; } - function onEventBanned() { - window.location.href = config.relative_path + '/'; + function onEventBanned(data) { + var message = data.until ? '[[error:user-banned-reason-until, ' + $.timeago(data.until) + ', ' + data.reason + ']]' : '[[error:user-banned-reason, ' + data.reason + ']]'; + + bootbox.alert({ + title: '[[error:user-banned]]', + message: message, + closeButton: false, + callback: function () { + window.location.href = config.relative_path + '/'; + }, + }); } }()); diff --git a/public/src/utils.js b/public/src/utils.js index 16a5bdbef7..8f2dcb8e87 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -4,10 +4,11 @@ if (typeof module === 'object' && module.exports) { var winston = require('winston'); - var file = require('../../src/file'); + module.exports = factory(require('xregexp')); module.exports.walk = function (dir, done) { // DEPRECATED + var file = require('../../src/file'); winston.warn('[deprecated] `utils.walk` is deprecated. Use `file.walk` instead.'); file.walk(dir, done); }; @@ -157,7 +158,7 @@ }, isRelativeUrl: function (url) { - var firstChar = url.slice(0, 1); + var firstChar = String(url || '').charAt(0); return (firstChar === '.' || firstChar === '/'); }, @@ -428,5 +429,11 @@ }; } + if (typeof String.prototype.rtrim !== 'function') { + String.prototype.rtrim = function () { + return this.replace(/\s+$/g, ''); + }; + } + return utils; })); diff --git a/src/analytics.js b/src/analytics.js index 6a733b1a5f..8e9871580b 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -16,17 +16,21 @@ var uniquevisitors = 0; var isCategory = /^(?:\/api)?\/category\/(\d+)/; -new cronJob('*/10 * * * *', function () { +new cronJob('*/10 * * * * *', function () { Analytics.writeData(); }, null, true); -Analytics.increment = function (keys) { +Analytics.increment = function (keys, callback) { keys = Array.isArray(keys) ? keys : [keys]; keys.forEach(function (key) { counters[key] = counters[key] || 0; counters[key] += 1; }); + + if (typeof callback === 'function') { + callback(); + } }; Analytics.pageView = function (payload) { diff --git a/src/batch.js b/src/batch.js index 475c4aa7ca..9a0271f29b 100644 --- a/src/batch.js +++ b/src/batch.js @@ -21,6 +21,15 @@ exports.processSortedSet = function (setKey, process, options, callback) { return callback(new Error('[[error:process-not-a-function]]')); } + // Progress bar handling (upgrade scripts) + if (options.progress) { + db.sortedSetCard(setKey, function (err, total) { + if (!err) { + options.progress.total = total; + } + }); + } + // 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); @@ -53,6 +62,7 @@ exports.processSortedSet = function (setKey, process, options, callback) { } start += utils.isNumber(options.alwaysStartAt) ? options.alwaysStartAt : batch + 1; stop = start + batch; + next(); }); }); diff --git a/src/bcrypt.js b/src/bcrypt.js index 40a493e75d..8cce80372e 100644 --- a/src/bcrypt.js +++ b/src/bcrypt.js @@ -9,7 +9,7 @@ process.on('message', function (msg) { if (msg.type === 'hash') { hashPassword(msg.password, msg.rounds); } else if (msg.type === 'compare') { - bcrypt.compare(msg.password, msg.hash, done); + bcrypt.compare(String(msg.password || ''), String(msg.hash || ''), done); } }); diff --git a/src/categories/create.js b/src/categories/create.js index 90380c0de0..eb64f91995 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -72,7 +72,7 @@ module.exports = function (Categories) { next(null, category); }, function (category, next) { - plugins.fireHook('action:category.create', category); + plugins.fireHook('action:category.create', { category: category }); next(null, category); }, ], callback); @@ -138,9 +138,20 @@ module.exports = function (Categories) { }; Categories.copyPrivilegesFrom = function (fromCid, toCid, callback) { - async.each(privileges.privilegeList, function (privilege, next) { - copyPrivilege(privilege, fromCid, toCid, next); - }, callback); + async.waterfall([ + function (next) { + plugins.fireHook('filter:categories.copyPrivilegesFrom', { + privileges: privileges.privilegeList, + fromCid: fromCid, + toCid: toCid, + }, next); + }, + function (data, next) { + async.each(data.privileges, function (privilege, next) { + copyPrivilege(privilege, data.fromCid, data.toCid, next); + }, next); + }, + ], callback); }; function copyPrivilege(privilege, fromCid, toCid, callback) { diff --git a/src/categories/delete.js b/src/categories/delete.js index 4aad25846f..a4215b5ff2 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -30,7 +30,7 @@ module.exports = function (Categories) { purgeCategory(cid, next); }, function (next) { - plugins.fireHook('action:category.delete', cid); + plugins.fireHook('action:category.delete', { cid: cid, uid: uid }); next(); }, ], callback); @@ -58,7 +58,7 @@ module.exports = function (Categories) { ], next); }, function (next) { - async.each(privileges.privilegeList, function (privilege, next) { + async.eachSeries(privileges.privilegeList, function (privilege, next) { groups.destroy('cid:' + cid + ':privileges:' + privilege, next); }, next); }, diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js index d659b0153f..eca16192c9 100644 --- a/src/controllers/accounts/edit.js +++ b/src/controllers/accounts/edit.js @@ -26,7 +26,7 @@ editController.get = function (req, res, callback) { userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10); userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads, 10) === 1; userData.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1; - userData.profileImageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; + userData.profileImageDimension = parseInt(meta.config.profileImageDimension, 10) || 200; userData.groups = userData.groups.filter(function (group) { return group && group.userTitleEnabled && !groups.isPrivilegeGroup(group.name) && group.name !== 'registered-users'; diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 2a40e4044d..6086bf4cf8 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -4,14 +4,16 @@ var async = require('async'); var validator = require('validator'); var winston = require('winston'); +var nconf = require('nconf'); var user = require('../../user'); var groups = require('../../groups'); var plugins = require('../../plugins'); var meta = require('../../meta'); var utils = require('../../utils'); +var privileges = require('../../privileges'); -var helpers = {}; +var helpers = module.exports; helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { async.waterfall([ @@ -60,6 +62,9 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { sso: function (next) { plugins.fireHook('filter:auth.list', { uid: uid, associations: [] }, next); }, + canBanUser: function (next) { + privileges.users.canBanUser(callerUID, uid, next); + }, }, next); }, function (results, next) { @@ -109,7 +114,7 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { userData.isAdminOrGlobalModeratorOrModerator = isAdmin || isGlobalModerator || isModerator; userData.isSelfOrAdminOrGlobalModerator = isSelf || isAdmin || isGlobalModerator; userData.canEdit = isAdmin || (isGlobalModerator && !results.isTargetAdmin); - userData.canBan = isAdmin || (isGlobalModerator && !results.isTargetAdmin); + userData.canBan = results.canBanUser; userData.canChangePassword = isAdmin || (isSelf && parseInt(meta.config['password:disableEdit'], 10) !== 1); userData.isSelf = isSelf; userData.isFollowing = results.isFollowing; @@ -119,7 +124,13 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { 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.concat(results.profile_menu.links), isSelf); + userData.profile_links = filterLinks(results.profile_links.concat(results.profile_menu.links), { + self: isSelf, + other: !isSelf, + moderator: isModerator, + globalMod: isGlobalModerator, + admin: isAdmin, + }); userData.sso = results.sso.associations; userData.status = user.getStatus(userData); @@ -138,7 +149,7 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { userData.birthday = validator.escape(String(userData.birthday || '')); userData.moderationNote = validator.escape(String(userData.moderationNote || '')); - userData['cover:url'] = userData['cover:url'] || require('../../coverPhoto').getDefaultProfileCover(userData.uid); + userData['cover:url'] = userData['cover:url'] ? (nconf.get('relative_path') + userData['cover:url']) : require('../../coverPhoto').getDefaultProfileCover(userData.uid); userData['cover:position'] = validator.escape(String(userData['cover:position'] || '50% 50%')); userData['username:disableEdit'] = !userData.isAdmin && parseInt(meta.config['username:disableEdit'], 10) === 1; userData['email:disableEdit'] = !userData.isAdmin && parseInt(meta.config['email:disableEdit'], 10) === 1; @@ -154,10 +165,29 @@ helpers.getBaseUser = function (userslug, callerUID, callback) { helpers.getUserDataByUserSlug(userslug, callerUID, callback); }; -function filterLinks(links, self) { - return links.filter(function (link) { - return link && (link.public || self); +function filterLinks(links, states) { + return links.filter(function (link, index) { + // "public" is the old property, if visibility is defined, discard `public` + if (link.hasOwnProperty('public') && !link.hasOwnProperty('visibility')) { + winston.warn('[account/profileMenu (' + link.id + ')] Use of the `.public` property is deprecated, use `visibility` now'); + return link && (link.public || states.self); + } + + // Default visibility + link.visibility = Object.assign({ + self: true, + other: true, + moderator: true, + globalMod: true, + admin: true, + }, link.visibility); + + // Iterate through states and permit if every test passes (or is not defined) + var permit = Object.keys(states).some(function (state) { + return states[state] === link.visibility[state]; + }); + + links[index].public = permit; + return permit; }); } - -module.exports = helpers; diff --git a/src/controllers/accounts/info.js b/src/controllers/accounts/info.js index e852ebc3df..7f9edb2462 100644 --- a/src/controllers/accounts/info.js +++ b/src/controllers/accounts/info.js @@ -2,14 +2,19 @@ var async = require('async'); +var db = require('../../database'); var user = require('../../user'); var helpers = require('../helpers'); var accountHelpers = require('./helpers'); +var pagination = require('../../pagination'); -var infoController = {}; +var infoController = module.exports; infoController.get = function (req, res, callback) { var userData; + var page = Math.max(1, req.query.page || 1); + var itemsPerPage = 10; + async.waterfall([ function (next) { accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next); @@ -19,11 +24,27 @@ infoController.get = function (req, res, callback) { if (!userData) { return callback(); } + + var start = (page - 1) * itemsPerPage; + var stop = start + itemsPerPage - 1; async.parallel({ history: async.apply(user.getModerationHistory, userData.uid), sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID), usernames: async.apply(user.getHistory, 'user:' + userData.uid + ':usernames'), emails: async.apply(user.getHistory, 'user:' + userData.uid + ':emails'), + notes: function (next) { + if (!userData.isAdminOrGlobalModeratorOrModerator) { + return setImmediate(next); + } + async.parallel({ + notes: function (next) { + user.getModerationNotes(userData.uid, start, stop, next); + }, + count: function (next) { + db.sortedSetCard('uid:' + userData.uid + ':moderation:notes', next); + }, + }, next); + }, }, next); }, ], function (err, data) { @@ -35,11 +56,15 @@ infoController.get = function (req, res, callback) { userData.sessions = data.sessions; userData.usernames = data.usernames; userData.emails = data.emails; + + if (userData.isAdminOrGlobalModeratorOrModerator) { + userData.moderationNotes = data.notes.notes; + var pageCount = Math.ceil(data.notes.count / itemsPerPage); + userData.pagination = pagination.create(page, pageCount, req.query); + } userData.title = '[[pages:account/info]]'; userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: '/user/' + userData.userslug }, { text: '[[user:account_info]]' }]); res.render('account/info', userData); }); }; - -module.exports = infoController; diff --git a/src/controllers/accounts/notifications.js b/src/controllers/accounts/notifications.js index 6184ec056d..9e1839b42b 100644 --- a/src/controllers/accounts/notifications.js +++ b/src/controllers/accounts/notifications.js @@ -1,23 +1,91 @@ 'use strict'; +var async = require('async'); + var user = require('../../user'); var helpers = require('../helpers'); +var plugins = require('../../plugins'); +var pagination = require('../../pagination'); -var notificationsController = {}; +var notificationsController = module.exports; notificationsController.get = function (req, res, next) { - user.notifications.getAll(req.uid, 0, 39, function (err, notifications) { - if (err) { - return next(err); - } - res.render('notifications', { - notifications: notifications, - nextStart: 40, - title: '[[pages:notifications]]', - breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[pages:notifications]]' }]), - }); - }); -}; + var regularFilters = [ + { name: '[[notifications:all]]', filter: '' }, + { name: '[[notifications:topics]]', filter: 'new-topic' }, + { name: '[[notifications:replies]]', filter: 'new-reply' }, + { name: '[[notifications:chat]]', filter: 'new-chat' }, + { name: '[[notifications:follows]]', filter: 'follow' }, + { name: '[[notifications:upvote]]', filter: 'upvote' }, + ]; + + var moderatorFilters = [ + { name: '[[notifications:new-flags]]', filter: 'new-post-flag' }, + { name: '[[notifications:my-flags]]', filter: 'my-flags' }, + { name: '[[notifications:bans]]', filter: 'ban' }, + ]; + + var filter = req.query.filter || ''; + var page = Math.max(1, req.query.page || 1); + var itemsPerPage = 20; + var start = (page - 1) * itemsPerPage; + var stop = start + itemsPerPage - 1; + var selectedFilter; + var pageCount = 1; + var allFilters = []; + + async.waterfall([ + function (next) { + async.parallel({ + filters: function (next) { + plugins.fireHook('filter:notifications.addFilters', { + regularFilters: regularFilters, + moderatorFilters: moderatorFilters, + uid: req.uid, + }, next); + }, + isPrivileged: function (next) { + user.isPrivileged(req.uid, next); + }, + }, next); + }, + function (data, _next) { + allFilters = data.filters.regularFilters; + if (data.isPrivileged) { + allFilters = allFilters.concat([ + { separator: true }, + ]).concat(data.filters.moderatorFilters); + } -module.exports = notificationsController; + selectedFilter = allFilters.find(function (filterData) { + filterData.selected = filterData.filter === filter; + return filterData.selected; + }); + + if (!selectedFilter) { + return next(); + } + + user.notifications.getAll(req.uid, selectedFilter.filter, _next); + }, + function (nids, next) { + pageCount = nids.length / itemsPerPage; + nids = nids.slice(start, stop + 1); + + user.notifications.getNotifications(nids, req.uid, next); + }, + function (notifications) { + res.render('notifications', { + notifications: notifications, + pagination: pagination.create(page, pageCount, req.query), + filters: allFilters, + regularFilters: regularFilters, + moderatorFilters: moderatorFilters, + selectedFilter: selectedFilter, + title: '[[pages:notifications]]', + breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[pages:notifications]]' }]), + }); + }, + ], next); +}; diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index d22e3256bf..450ee92f60 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -82,9 +82,6 @@ profileController.get = function (req, res, callback) { var pageCount = Math.ceil(userData.postcount / itemsPerPage); userData.pagination = pagination.create(page, pageCount, req.query); - userData['cover:url'] = userData['cover:url'] || require('../../coverPhoto').getDefaultProfileCover(userData.uid); - userData['cover:position'] = userData['cover:position'] || '50% 50%'; - if (!parseInt(userData.profileviews, 10)) { userData.profileviews = 1; } diff --git a/src/controllers/admin.js b/src/controllers/admin.js index 056a7025aa..91922bec0b 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -4,7 +4,6 @@ var adminController = { dashboard: require('./admin/dashboard'), categories: require('./admin/categories'), tags: require('./admin/tags'), - flags: require('./admin/flags'), blacklist: require('./admin/blacklist'), groups: require('./admin/groups'), appearance: require('./admin/appearance'), diff --git a/src/controllers/admin/cache.js b/src/controllers/admin/cache.js index dce16818f8..dea5f0345b 100644 --- a/src/controllers/admin/cache.js +++ b/src/controllers/admin/cache.js @@ -5,6 +5,7 @@ var cacheController = {}; cacheController.get = function (req, res) { var postCache = require('../../posts/cache'); var groupCache = require('../../groups').cache; + var userSettingsCache = require('../../user').settingsCache; var avgPostSize = 0; var percentFull = 0; @@ -21,6 +22,12 @@ cacheController.get = function (req, res) { percentFull: percentFull, avgPostSize: avgPostSize, }, + userSettingsCache: { + length: userSettingsCache.length, + max: userSettingsCache.max, + itemCount: userSettingsCache.itemCount, + percentFull: ((userSettingsCache.length / userSettingsCache.max) * 100).toFixed(2), + }, groupCache: { length: groupCache.length, max: groupCache.max, diff --git a/src/controllers/admin/flags.js b/src/controllers/admin/flags.js deleted file mode 100644 index 2191394b57..0000000000 --- a/src/controllers/admin/flags.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict'; - -var async = require('async'); -var validator = require('validator'); - -var posts = require('../../posts'); -var user = require('../../user'); -var categories = require('../../categories'); -var analytics = require('../../analytics'); -var pagination = require('../../pagination'); - -var flagsController = {}; - -var itemsPerPage = 20; - -flagsController.get = function (req, res, next) { - var byUsername = req.query.byUsername || ''; - var cid = req.query.cid || 0; - var sortBy = req.query.sortBy || 'count'; - var page = parseInt(req.query.page, 10) || 1; - - async.parallel({ - categories: function (next) { - categories.buildForSelect(req.uid, next); - }, - flagData: function (next) { - getFlagData(req, res, next); - }, - analytics: function (next) { - analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30, next); - }, - assignees: async.apply(user.getAdminsandGlobalModsandModerators), - }, function (err, results) { - if (err) { - return next(err); - } - - // Minimise data set for assignees so tjs does less work - results.assignees = results.assignees.map(function (userObj) { - return { - uid: userObj.uid, - username: userObj.username, - }; - }); - - // If res.locals.cids is populated, then slim down the categories list - if (res.locals.cids) { - results.categories = results.categories.filter(function (category) { - return res.locals.cids.indexOf(String(category.cid)) !== -1; - }); - } - - var pageCount = Math.max(1, Math.ceil(results.flagData.count / itemsPerPage)); - - results.categories.forEach(function (category) { - category.selected = parseInt(category.cid, 10) === parseInt(cid, 10); - }); - - var data = { - posts: results.flagData.posts, - assignees: results.assignees, - analytics: results.analytics, - categories: results.categories, - byUsername: validator.escape(String(byUsername)), - sortByCount: sortBy === 'count', - sortByTime: sortBy === 'time', - pagination: pagination.create(page, pageCount, req.query), - }; - res.render('admin/manage/flags', data); - }); -}; - -function getFlagData(req, res, callback) { - var sortBy = req.query.sortBy || 'count'; - var byUsername = req.query.byUsername || ''; - var cid = req.query.cid || res.locals.cids || 0; - var page = parseInt(req.query.page, 10) || 1; - var start = (page - 1) * itemsPerPage; - var stop = start + itemsPerPage - 1; - - var sets = [sortBy === 'count' ? 'posts:flags:count' : 'posts:flagged']; - - async.waterfall([ - function (next) { - if (byUsername) { - user.getUidByUsername(byUsername, next); - } else { - process.nextTick(next, null, 0); - } - }, - function (uid, next) { - if (uid) { - sets.push('uid:' + uid + ':flag:pids'); - } - - posts.getFlags(sets, cid, req.uid, start, stop, next); - }, - ], callback); -} - - -module.exports = flagsController; diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index 88fef98b4b..734d151a3d 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -16,22 +16,30 @@ var info = {}; infoController.get = function (req, res) { info = {}; pubsub.publish('sync:node:info:start'); + var timeoutMS = 1000; setTimeout(function () { var data = []; Object.keys(info).forEach(function (key) { data.push(info[key]); }); data.sort(function (a, b) { - if (a.os.hostname < b.os.hostname) { + if (a.id < b.id) { return -1; } - if (a.os.hostname > b.os.hostname) { + if (a.id > b.id) { return 1; } return 0; }); - res.render('admin/development/info', { info: data, infoJSON: JSON.stringify(data, null, 4), host: os.hostname(), port: nconf.get('port') }); - }, 500); + res.render('admin/development/info', { + info: data, + infoJSON: JSON.stringify(data, null, 4), + host: os.hostname(), + port: nconf.get('port'), + nodeCount: data.length, + timeout: timeoutMS, + }); + }, timeoutMS); }; pubsub.on('sync:node:info:start', function () { @@ -39,7 +47,8 @@ pubsub.on('sync:node:info:start', function () { if (err) { return winston.error(err); } - pubsub.publish('sync:node:info:end', { data: data, id: os.hostname() + ':' + nconf.get('port') }); + data.id = os.hostname() + ':' + nconf.get('port'); + pubsub.publish('sync:node:info:end', { data: data, id: data.id }); }); }); diff --git a/src/controllers/admin/languages.js b/src/controllers/admin/languages.js index 0ac4e98e99..e2d848ddae 100644 --- a/src/controllers/admin/languages.js +++ b/src/controllers/admin/languages.js @@ -18,6 +18,7 @@ languagesController.get = function (req, res, next) { res.render('admin/general/languages', { languages: languages, + autoDetectLang: parseInt(meta.config.autoDetectLang, 10) === 1, }); }); }; diff --git a/src/controllers/admin/themes.js b/src/controllers/admin/themes.js index 598cd7cf94..94fdf43746 100644 --- a/src/controllers/admin/themes.js +++ b/src/controllers/admin/themes.js @@ -7,9 +7,9 @@ var themesController = {}; themesController.get = function (req, res, next) { var themeDir = path.join(__dirname, '../../../node_modules/' + req.params.theme); - file.exists(themeDir, function (exists) { - if (!exists) { - return next(); + file.exists(themeDir, function (err, exists) { + if (err || !exists) { + return next(err); } var themeConfig = require(path.join(themeDir, 'theme.json')); diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index 02bd065c0b..4280a2793b 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -5,6 +5,7 @@ var path = require('path'); var async = require('async'); var nconf = require('nconf'); var winston = require('winston'); +var mime = require('mime'); var meta = require('../../meta'); var file = require('../../file'); @@ -102,6 +103,11 @@ uploadsController.uploadLogo = function (req, res, next) { uploadsController.uploadSound = function (req, res, next) { var uploadedFile = req.files.files[0]; + var mimeType = mime.lookup(uploadedFile.name); + if (!/^audio\//.test(mimeType)) { + return next(Error('[[error:invalid-data]]')); + } + file.saveFileToLocal(uploadedFile.name, 'sounds', uploadedFile.path, function (err) { if (err) { return next(err); diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index 54d9ab93de..7dd08994cc 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -127,8 +127,10 @@ function getUsers(set, section, min, max, req, res, next) { count: function (next) { if (byScore) { db.sortedSetCount(set, min, max, next); - } else { + } else if (set === 'users:banned' || set === 'users:notvalidated') { db.sortedSetCard(set, next); + } else { + db.getObjectField('global', 'userCount', next); } }, users: function (next) { diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 396aafd130..09cb5837b2 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -13,10 +13,11 @@ var user = require('../user'); var plugins = require('../plugins'); var utils = require('../utils'); var Password = require('../password'); +var translator = require('../translator'); var sockets = require('../socket.io'); -var authenticationController = {}; +var authenticationController = module.exports; authenticationController.register = function (req, res) { var registrationType = meta.config.registrationType || 'normal'; @@ -330,7 +331,7 @@ authenticationController.onSuccessfulLogin = function (req, uid, callback) { // Force session check for all connected socket.io clients with the same session id sockets.in('sess_' + req.sessionID).emit('checkSession', uid); - plugins.fireHook('action:user.loggedIn', uid); + plugins.fireHook('action:user.loggedIn', { uid: uid, req: req }); callback(); }); }; @@ -344,21 +345,21 @@ authenticationController.localLogin = function (req, username, password, next) { var uid; var userData = {}; + if (!password || !utils.isPasswordValid(password)) { + return next(new Error('[[error:invalid-password]]')); + } + + if (password.length > 4096) { + return next(new Error('[[error:password-too-long]]')); + } + async.waterfall([ - function (next) { - user.isPasswordValid(password, next); - }, function (next) { user.getUidByUserslug(userslug, next); }, function (_uid, next) { - if (!_uid) { - return next(new Error('[[error:no-user]]')); - } uid = _uid; - user.auth.logAttempt(uid, req.ip, next); - }, - function (next) { + async.parallel({ userData: function (next) { db.getObjectFields('user:' + uid, ['password', 'passwordExpiry'], next); @@ -379,31 +380,19 @@ authenticationController.localLogin = function (req, username, password, next) { if (!result.isAdmin && parseInt(meta.config.allowLocalLogin, 10) === 0) { return next(new Error('[[error:local-login-disabled]]')); } - if (!userData || !userData.password) { - return next(new Error('[[error:invalid-user-data]]')); - } + if (result.banned) { - // Retrieve ban reason and show error - return user.getLatestBanInfo(uid, function (err, banInfo) { - if (err) { - if (err.message === 'no-ban-info') { - next(new Error('[[error:user-banned]]')); - } else { - next(err); - } - } else if (banInfo.reason) { - next(new Error('[[error:user-banned-reason, ' + banInfo.reason + ']]')); - } else { - next(new Error('[[error:user-banned]]')); - } - }); + return banUser(uid, next); } + user.auth.logAttempt(uid, req.ip, next); + }, + function (next) { Password.compare(password, userData.password, next); }, function (passwordMatch, next) { if (!passwordMatch) { - return next(new Error('[[error:invalid-password]]')); + return next(new Error('[[error:invalid-login-credentials]]')); } user.auth.clearLoginAttempts(uid); next(null, userData, '[[success:authentication-successful]]'); @@ -437,5 +426,23 @@ authenticationController.logout = function (req, res, next) { } }; +function banUser(uid, next) { + user.getLatestBanInfo(uid, function (err, banInfo) { + if (err) { + if (err.message === 'no-ban-info') { + return next(new Error('[[error:user-banned]]')); + } -module.exports = authenticationController; + return next(err); + } + + if (!banInfo.reason) { + translator.translate('[[user:info.banned-no-reason]]', function (translated) { + banInfo.reason = translated; + next(new Error(banInfo.expiry ? '[[error:user-banned-reason-until, ' + banInfo.expiry_readable + ', ' + banInfo.reason + ']]' : '[[error:user-banned-reason, ' + banInfo.reason + ']]')); + }); + } else { + next(new Error(banInfo.expiry ? '[[error:user-banned-reason-until, ' + banInfo.expiry_readable + ', ' + banInfo.reason + ']]' : '[[error:user-banned-reason, ' + banInfo.reason + ']]')); + } + }); +} diff --git a/src/controllers/categories.js b/src/controllers/categories.js index e02f107b4a..9a17a47fc2 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -21,17 +21,6 @@ categoriesController.list = function (req, res, next) { content: 'website', }]; - var ogImage = meta.config['og:image'] || meta.config['brand:logo'] || ''; - if (ogImage) { - if (!ogImage.startsWith('http')) { - ogImage = nconf.get('url') + ogImage; - } - res.locals.metaTags.push({ - property: 'og:image', - content: ogImage, - }); - } - var categoryData; async.waterfall([ function (next) { diff --git a/src/controllers/index.js b/src/controllers/index.js index 6cebf29d8f..0f15d2f4ab 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -370,7 +370,3 @@ Controllers.termsOfUse = function (req, res, next) { termsOfUse: meta.config.termsOfUse, }); }; - -Controllers.ping = function (req, res) { - res.status(200).send(req.path === '/sping' ? 'healthy' : '200'); -}; diff --git a/src/controllers/mods.js b/src/controllers/mods.js index 6a1835f980..6365650323 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -3,24 +3,125 @@ var async = require('async'); var user = require('../user'); -var adminFlagsController = require('./admin/flags'); +var categories = require('../categories'); +var flags = require('../flags'); +var analytics = require('../analytics'); -var modsController = {}; +var modsController = { + flags: {}, +}; -modsController.flagged = function (req, res, next) { +modsController.flags.list = function (req, res, next) { async.parallel({ isAdminOrGlobalMod: async.apply(user.isAdminOrGlobalMod, req.uid), moderatedCids: async.apply(user.getModeratedCids, req.uid), }, function (err, results) { - if (err || !(results.isAdminOrGlobalMod || !!results.moderatedCids.length)) { + if (err) { return next(err); + } else if (!(results.isAdminOrGlobalMod || !!results.moderatedCids.length)) { + return next(new Error('[[error:no-privileges]]')); } if (!results.isAdminOrGlobalMod && results.moderatedCids.length) { res.locals.cids = results.moderatedCids; } - adminFlagsController.get(req, res, next); + // Parse query string params for filters + var hasFilter = false; + var valid = ['assignee', 'state', 'reporterId', 'type', 'targetUid', 'cid', 'quick']; + var filters = valid.reduce(function (memo, cur) { + if (req.query.hasOwnProperty(cur)) { + memo[cur] = req.query[cur]; + } + + return memo; + }, {}); + hasFilter = !!Object.keys(filters).length; + + if (res.locals.cids) { + if (!filters.cid) { + // If mod and no cid filter, add filter for their modded categories + filters.cid = res.locals.cids; + } else if (Array.isArray(filters.cid)) { + // Remove cids they do not moderate + filters.cid = filters.cid.filter(function (cid) { + return res.locals.cids.indexOf(String(cid)) !== -1; + }); + } else if (res.locals.cids.indexOf(String(filters.cid)) === -1) { + filters.cid = res.locals.cids; + hasFilter = false; + } + } + + async.parallel({ + flags: async.apply(flags.list, filters, req.uid), + analytics: async.apply(analytics.getDailyStatsForSet, 'analytics:flags', Date.now(), 30), + categories: async.apply(categories.buildForSelect, req.uid), + }, function (err, data) { + if (err) { + return next(err); + } + + // If res.locals.cids is populated, then slim down the categories list + if (res.locals.cids) { + data.categories = data.categories.filter(function (category) { + return res.locals.cids.indexOf(String(category.cid)) !== -1; + }); + } + + // Minimal returned set for templates.js + data.categories = data.categories.reduce(function (memo, cur) { + if (!res.locals.cids) { + memo[cur.cid] = cur.name; + return memo; + } + + // If mod, remove categories they can't moderate + if (res.locals.cids.indexOf(String(cur.cid)) !== -1) { + memo[cur.cid] = cur.name; + } + + return memo; + }, {}); + + res.render('flags/list', { + flags: data.flags, + analytics: data.analytics, + categories: data.categories, + hasFilter: hasFilter, + filters: filters, + title: '[[pages:flags]]', + }); + }); + }); +}; + +modsController.flags.detail = function (req, res, next) { + async.parallel({ + isAdminOrGlobalMod: async.apply(user.isAdminOrGlobalMod, req.uid), + moderatedCids: async.apply(user.getModeratedCids, req.uid), + flagData: async.apply(flags.get, req.params.flagId), + assignees: async.apply(user.getAdminsandGlobalModsandModerators), + }, function (err, results) { + if (err || !results.flagData) { + return next(err || new Error('[[error:invalid-data]]')); + } else if (!(results.isAdminOrGlobalMod || !!results.moderatedCids.length)) { + return next(new Error('[[error:no-privileges]]')); + } + + res.render('flags/detail', Object.assign(results.flagData, { + assignees: results.assignees, + type_bool: ['post', 'user', 'empty'].reduce(function (memo, cur) { + if (cur !== 'empty') { + memo[cur] = results.flagData.type === cur && !!Object.keys(results.flagData.target).length; + } else { + memo[cur] = !Object.keys(results.flagData.target).length; + } + + return memo; + }, {}), + title: '[[pages:flag-details, ' + req.params.flagId + ']]', + })); }); }; diff --git a/src/controllers/search.js b/src/controllers/search.js index 29a883489f..bf818a39d5 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -38,7 +38,7 @@ searchController.search = function (req, res, next) { repliesFilter: req.query.repliesFilter, timeRange: req.query.timeRange, timeFilter: req.query.timeFilter, - sortBy: req.query.sortBy, + sortBy: req.query.sortBy || meta.config.searchDefaultSortBy || '', sortDirection: req.query.sortDirection, page: page, uid: req.uid, @@ -60,13 +60,14 @@ searchController.search = function (req, res, next) { var searchData = results.search; searchData.categories = categoriesData; - searchData.categoriesCount = results.categories.length; + searchData.categoriesCount = Math.max(10, Math.min(20, categoriesData.length)); searchData.pagination = pagination.create(page, searchData.pageCount, req.query); searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts'; searchData.showAsTopics = req.query.showAs === 'topics'; searchData.title = '[[global:header.search]]'; searchData.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[global:search]]' }]); searchData.expandSearch = !req.query.term; + searchData.searchDefaultSortBy = meta.config.searchDefaultSortBy || ''; res.render('search', searchData); }); diff --git a/src/controllers/tags.js b/src/controllers/tags.js index cffff0e44c..64e1640706 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -1,8 +1,6 @@ 'use strict'; - var async = require('async'); -var nconf = require('nconf'); var validator = require('validator'); var user = require('../user'); @@ -63,10 +61,6 @@ tagsController.getTag = function (req, res, next) { property: 'og:title', content: tag, }, - { - property: 'og:url', - content: nconf.get('url') + '/tags/' + tag, - }, ]; templateData.topics = topics; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 4fe1bf96d5..ddd6fd889a 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -206,11 +206,6 @@ topicsController.get = function (req, res, callback) { property: 'og:type', content: 'article', }, - { - property: 'og:url', - content: nconf.get('url') + '/topic/' + topicData.slug + (req.params.post_index ? ('/' + req.params.post_index) : ''), - noEscape: true, - }, { property: 'og:image', content: ogImageUrl, diff --git a/src/controllers/unread.js b/src/controllers/unread.js index c5d0d4d950..5e00cf5c96 100644 --- a/src/controllers/unread.js +++ b/src/controllers/unread.js @@ -40,7 +40,15 @@ unreadController.get = function (req, res, next) { settings = results.settings; var start = Math.max(0, (page - 1) * settings.topicsPerPage); var stop = start + settings.topicsPerPage - 1; - topics.getUnreadTopics(cid, req.uid, start, stop, filter, next); + var cutoff = req.session.unreadCutoff ? req.session.unreadCutoff : topics.unreadCutoff(); + topics.getUnreadTopics({ + cid: cid, + uid: req.uid, + start: start, + stop: stop, + filter: filter, + cutoff: cutoff, + }, next); }, ], function (err, data) { if (err) { diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 82556ee12c..3ef09c89cc 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -33,7 +33,7 @@ uploadsController.upload = function (req, res, filesIterator) { return res.status(500).json({ path: req.path, error: err.message }); } - res.status(200).send(images); + res.status(200).json(images); }); }; @@ -208,20 +208,19 @@ uploadsController.uploadFile = function (uid, uploadedFile, callback) { return callback(new Error('[[error:file-too-big, ' + meta.config.maximumFileSize + ']]')); } - if (meta.config.hasOwnProperty('allowedFileExtensions')) { - var allowed = file.allowedExtensions(); - var extension = file.typeToExtension(uploadedFile.type); - if (!extension || (allowed.length > 0 && allowed.indexOf(extension) === -1)) { - return callback(new Error('[[error:invalid-file-type, ' + allowed.join(', ') + ']]')); - } + var allowed = file.allowedExtensions(); + + var extension = path.extname(uploadedFile.name).toLowerCase(); + if (!extension || extension === '.' || (allowed.length > 0 && allowed.indexOf(extension) === -1)) { + return callback(new Error('[[error:invalid-file-type, ' + allowed.join(', ') + ']]')); } saveFileToLocal(uploadedFile, callback); }; function saveFileToLocal(uploadedFile, callback) { - var extension = file.typeToExtension(uploadedFile.type); - if (!extension) { + var extension = path.extname(uploadedFile.name); + if (!extension || extension === '.') { return callback(new Error('[[error:invalid-extension]]')); } var filename = uploadedFile.name || 'upload'; diff --git a/src/database/mongo.js b/src/database/mongo.js index be13a185e8..273ec18001 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -1,239 +1,244 @@ 'use strict'; -(function (module) { - var winston = require('winston'); - var async = require('async'); - var nconf = require('nconf'); - var session = require('express-session'); - var _ = require('underscore'); - var semver = require('semver'); - var db; - - _.mixin(require('underscore.deep')); - - module.questions = [ - { - name: 'mongo:host', - description: 'Host IP or address of your MongoDB instance', - default: nconf.get('mongo:host') || '127.0.0.1', - }, - { - name: 'mongo:port', - description: 'Host port of your MongoDB instance', - default: nconf.get('mongo:port') || 27017, - }, - { - name: 'mongo:username', - description: 'MongoDB username', - default: nconf.get('mongo:username') || '', - }, - { - name: 'mongo:password', - description: 'Password of your MongoDB database', - hidden: true, - default: nconf.get('mongo:password') || '', - before: function (value) { value = value || nconf.get('mongo:password') || ''; return value; }, - }, - { - name: 'mongo:database', - description: 'MongoDB database name', - default: nconf.get('mongo:database') || 'nodebb', - }, - ]; - - module.helpers = module.helpers || {}; - module.helpers.mongo = require('./mongo/helpers'); - - module.init = function (callback) { - callback = callback || function () { }; - - var mongoClient = require('mongodb').MongoClient; - - var usernamePassword = ''; - if (nconf.get('mongo:username') && nconf.get('mongo:password')) { - usernamePassword = nconf.get('mongo:username') + ':' + encodeURIComponent(nconf.get('mongo:password')) + '@'; - } - // Sensible defaults for Mongo, if not set - if (!nconf.get('mongo:host')) { - nconf.set('mongo:host', '127.0.0.1'); - } - if (!nconf.get('mongo:port')) { - nconf.set('mongo:port', 27017); - } - if (!nconf.get('mongo:database')) { - nconf.set('mongo:database', 'nodebb'); - } +var winston = require('winston'); +var async = require('async'); +var nconf = require('nconf'); +var session = require('express-session'); +var _ = require('underscore'); +var semver = require('semver'); +var db; + +_.mixin(require('underscore.deep')); + +var mongoModule = module.exports; + +mongoModule.questions = [ + { + name: 'mongo:host', + description: 'Host IP or address of your MongoDB instance', + default: nconf.get('mongo:host') || '127.0.0.1', + }, + { + name: 'mongo:port', + description: 'Host port of your MongoDB instance', + default: nconf.get('mongo:port') || 27017, + }, + { + name: 'mongo:username', + description: 'MongoDB username', + default: nconf.get('mongo:username') || '', + }, + { + name: 'mongo:password', + description: 'Password of your MongoDB database', + hidden: true, + default: nconf.get('mongo:password') || '', + before: function (value) { value = value || nconf.get('mongo:password') || ''; return value; }, + }, + { + name: 'mongo:database', + description: 'MongoDB database name', + default: nconf.get('mongo:database') || 'nodebb', + }, +]; + +mongoModule.helpers = mongoModule.helpers || {}; +mongoModule.helpers.mongo = require('./mongo/helpers'); + +mongoModule.init = function (callback) { + callback = callback || function () { }; + + var mongoClient = require('mongodb').MongoClient; + + var usernamePassword = ''; + if (nconf.get('mongo:username') && nconf.get('mongo:password')) { + usernamePassword = nconf.get('mongo:username') + ':' + encodeURIComponent(nconf.get('mongo:password')) + '@'; + } + + // Sensible defaults for Mongo, if not set + if (!nconf.get('mongo:host')) { + nconf.set('mongo:host', '127.0.0.1'); + } + if (!nconf.get('mongo:port')) { + nconf.set('mongo:port', 27017); + } + if (!nconf.get('mongo:database')) { + nconf.set('mongo:database', 'nodebb'); + } + + var hosts = nconf.get('mongo:host').split(','); + var ports = nconf.get('mongo:port').toString().split(','); + var servers = []; + + for (var i = 0; i < hosts.length; i += 1) { + servers.push(hosts[i] + ':' + ports[i]); + } + + var connString = 'mongodb://' + usernamePassword + servers.join() + '/' + nconf.get('mongo:database'); + + var connOptions = { + poolSize: 10, + reconnectTries: 3600, + reconnectInterval: 1000, + autoReconnect: true, + }; - var hosts = nconf.get('mongo:host').split(','); - var ports = nconf.get('mongo:port').toString().split(','); - var servers = []; + connOptions = _.deepExtend(connOptions, nconf.get('mongo:options') || {}); - for (var i = 0; i < hosts.length; i += 1) { - servers.push(hosts[i] + ':' + ports[i]); + mongoClient.connect(connString, connOptions, function (err, _db) { + if (err) { + winston.error('NodeBB could not connect to your Mongo database. Mongo returned the following error: ' + err.message); + return callback(err); } - var connString = 'mongodb://' + usernamePassword + servers.join() + '/' + nconf.get('mongo:database'); - - var connOptions = { - poolSize: 10, - reconnectTries: 3600, - reconnectInterval: 1000, - autoReconnect: true, - }; - - connOptions = _.deepExtend(connOptions, nconf.get('mongo:options') || {}); - - mongoClient.connect(connString, connOptions, function (err, _db) { - if (err) { - winston.error('NodeBB could not connect to your Mongo database. Mongo returned the following error: ' + err.message); - return callback(err); - } - - db = _db; - - module.client = db; - - require('./mongo/main')(db, module); - require('./mongo/hash')(db, module); - require('./mongo/sets')(db, module); - require('./mongo/sorted')(db, module); - require('./mongo/list')(db, module); - - if (nconf.get('mongo:password') && nconf.get('mongo:username')) { - db.authenticate(nconf.get('mongo:username'), nconf.get('mongo:password'), function (err) { - callback(err); - }); - } else { - winston.warn('You have no mongo password setup!'); - callback(); - } - }); - }; - - module.initSessionStore = function (callback) { - var meta = require('../meta'); - var sessionStore; + db = _db; - var ttl = meta.getSessionTTLSeconds(); + mongoModule.client = db; - if (nconf.get('redis')) { - sessionStore = require('connect-redis')(session); - var rdb = require('./redis'); - rdb.client = rdb.connect(); + require('./mongo/main')(db, mongoModule); + require('./mongo/hash')(db, mongoModule); + require('./mongo/sets')(db, mongoModule); + require('./mongo/sorted')(db, mongoModule); + require('./mongo/list')(db, mongoModule); - module.sessionStore = new sessionStore({ - client: rdb.client, - ttl: ttl, - }); - } else if (nconf.get('mongo')) { - sessionStore = require('connect-mongo')(session); - module.sessionStore = new sessionStore({ - db: db, - ttl: ttl, + if (nconf.get('mongo:password') && nconf.get('mongo:username')) { + db.authenticate(nconf.get('mongo:username'), nconf.get('mongo:password'), function (err) { + callback(err); }); + } else { + winston.warn('You have no mongo password setup!'); + callback(); } + }); +}; - callback(); - }; +mongoModule.initSessionStore = function (callback) { + var meta = require('../meta'); + var sessionStore; - module.createIndices = function (callback) { - function createIndex(collection, index, options, callback) { - module.client.collection(collection).createIndex(index, options, callback); - } + var ttl = meta.getSessionTTLSeconds(); - if (!module.client) { - winston.warn('[database/createIndices] database not initialized'); - return callback(); - } + if (nconf.get('redis')) { + sessionStore = require('connect-redis')(session); + var rdb = require('./redis'); + rdb.client = rdb.connect(); - winston.info('[database] Checking database indices.'); - async.series([ - async.apply(createIndex, 'objects', { _key: 1, score: -1 }, { background: true }), - async.apply(createIndex, 'objects', { _key: 1, value: -1 }, { background: true, unique: true, sparse: true }), - async.apply(createIndex, 'objects', { expireAt: 1 }, { expireAfterSeconds: 0, background: true }), - ], function (err) { - if (err) { - winston.error('Error creating index ' + err.message); - return callback(err); - } - winston.info('[database] Checking database indices done!'); - callback(); + mongoModule.sessionStore = new sessionStore({ + client: rdb.client, + ttl: ttl, }); - }; - - module.checkCompatibility = function (callback) { - var mongoPkg = require('mongodb/package.json'); - - if (semver.lt(mongoPkg.version, '2.0.0')) { - return callback(new Error('The `mongodb` package is out-of-date, please run `./nodebb setup` again.')); + } else if (nconf.get('mongo')) { + sessionStore = require('connect-mongo')(session); + mongoModule.sessionStore = new sessionStore({ + db: db, + ttl: ttl, + }); + } + + callback(); +}; + +mongoModule.createIndices = function (callback) { + function createIndex(collection, index, options, callback) { + mongoModule.client.collection(collection).createIndex(index, options, callback); + } + + if (!mongoModule.client) { + winston.warn('[database/createIndices] database not initialized'); + return callback(); + } + + winston.info('[database] Checking database indices.'); + async.series([ + async.apply(createIndex, 'objects', { _key: 1, score: -1 }, { background: true }), + async.apply(createIndex, 'objects', { _key: 1, value: -1 }, { background: true, unique: true, sparse: true }), + async.apply(createIndex, 'objects', { expireAt: 1 }, { expireAfterSeconds: 0, background: true }), + ], function (err) { + if (err) { + winston.error('Error creating index ' + err.message); + return callback(err); } - + winston.info('[database] Checking database indices done!'); callback(); - }; - - module.info = function (db, callback) { - if (!db) { - return callback(); - } - async.parallel({ - serverStatus: function (next) { - db.command({ serverStatus: 1 }, next); - }, - stats: function (next) { - db.command({ dbStats: 1 }, next); - }, - listCollections: function (next) { - db.listCollections().toArray(function (err, items) { - if (err) { - return next(err); - } - async.map(items, function (collection, next) { - db.collection(collection.name).stats(next); - }, next); - }); - }, - }, function (err, results) { - if (err) { - return callback(err); - } - var stats = results.stats; - var scale = 1024 * 1024; - - results.listCollections = results.listCollections.map(function (collectionInfo) { - return { - name: collectionInfo.ns, - count: collectionInfo.count, - size: collectionInfo.size, - avgObjSize: collectionInfo.avgObjSize, - storageSize: collectionInfo.storageSize, - totalIndexSize: collectionInfo.totalIndexSize, - indexSizes: collectionInfo.indexSizes, - }; + }); +}; + +mongoModule.checkCompatibility = function (callback) { + var mongoPkg = require('mongodb/package.json'); + + if (semver.lt(mongoPkg.version, '2.0.0')) { + return callback(new Error('The `mongodb` package is out-of-date, please run `./nodebb setup` again.')); + } + + callback(); +}; + +mongoModule.info = function (db, callback) { + if (!db) { + return callback(); + } + async.parallel({ + serverStatus: function (next) { + db.command({ serverStatus: 1 }, next); + }, + stats: function (next) { + db.command({ dbStats: 1 }, next); + }, + listCollections: function (next) { + db.listCollections().toArray(function (err, items) { + if (err) { + return next(err); + } + async.map(items, function (collection, next) { + db.collection(collection.name).stats(next); + }, next); }); - - stats.mem = results.serverStatus.mem; - stats.collectionData = results.listCollections; - stats.network = results.serverStatus.network; - stats.raw = JSON.stringify(stats, null, 4); - - stats.avgObjSize = stats.avgObjSize.toFixed(2); - stats.dataSize = (stats.dataSize / scale).toFixed(2); - stats.storageSize = (stats.storageSize / scale).toFixed(2); - stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(2) : 0; - stats.indexSize = (stats.indexSize / scale).toFixed(2); - stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1'; - stats.host = results.serverStatus.host; - stats.version = results.serverStatus.version; - stats.uptime = results.serverStatus.uptime; - stats.mongo = true; - - callback(null, stats); + }, + }, function (err, results) { + if (err) { + return callback(err); + } + var stats = results.stats; + var scale = 1024 * 1024 * 1024; + + results.listCollections = results.listCollections.map(function (collectionInfo) { + return { + name: collectionInfo.ns, + count: collectionInfo.count, + size: collectionInfo.size, + avgObjSize: collectionInfo.avgObjSize, + storageSize: collectionInfo.storageSize, + totalIndexSize: collectionInfo.totalIndexSize, + indexSizes: collectionInfo.indexSizes, + }; }); - }; - module.close = function () { - db.close(); - }; -}(exports)); + stats.mem = results.serverStatus.mem; + stats.mem = results.serverStatus.mem; + stats.mem.resident = (stats.mem.resident / 1024).toFixed(2); + stats.mem.virtual = (stats.mem.virtual / 1024).toFixed(2); + stats.mem.mapped = (stats.mem.mapped / 1024).toFixed(2); + stats.collectionData = results.listCollections; + stats.network = results.serverStatus.network; + stats.raw = JSON.stringify(stats, null, 4); + + stats.avgObjSize = stats.avgObjSize.toFixed(2); + stats.dataSize = (stats.dataSize / scale).toFixed(2); + stats.storageSize = (stats.storageSize / scale).toFixed(2); + stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(2) : 0; + stats.indexSize = (stats.indexSize / scale).toFixed(2); + stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1'; + stats.host = results.serverStatus.host; + stats.version = results.serverStatus.version; + stats.uptime = results.serverStatus.uptime; + stats.mongo = true; + + callback(null, stats); + }); +}; + +mongoModule.close = function () { + db.close(); +}; diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 57c72cdc91..997f22cfc0 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -8,7 +8,9 @@ module.exports = function (db, module) { if (!key || !data) { return callback(); } - + if (data.hasOwnProperty('')) { + delete data['']; + } db.collection('objects').update({ _key: key }, { $set: data }, { upsert: true, w: 1 }, function (err) { callback(err); }); diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 59bd05b9fd..4168199bb5 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -241,7 +241,7 @@ module.exports = function (db, module) { module.sortedSetScore = function (key, value, callback) { if (!key) { - return callback(); + return callback(null, null); } value = helpers.valueToString(value); db.collection('objects').findOne({ _key: key, value: value }, { fields: { _id: 0, score: 1 } }, function (err, result) { @@ -274,7 +274,7 @@ module.exports = function (db, module) { module.sortedSetScores = function (key, values, callback) { if (!key) { - return callback(); + return callback(null, null); } values = values.map(helpers.valueToString); db.collection('objects').find({ _key: key, value: { $in: values } }, { _id: 0, value: 1, score: 1 }).toArray(function (err, result) { diff --git a/src/database/redis.js b/src/database/redis.js index 7fb61565e3..9ebc154705 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -1,166 +1,164 @@ 'use strict'; -(function (module) { - var winston = require('winston'); - var nconf = require('nconf'); - var semver = require('semver'); - var session = require('express-session'); - var redis; - var redisClient; - - module.questions = [ - { - name: 'redis:host', - description: 'Host IP or address of your Redis instance', - default: nconf.get('redis:host') || '127.0.0.1', - }, - { - name: 'redis:port', - description: 'Host port of your Redis instance', - default: nconf.get('redis:port') || 6379, - }, - { - name: 'redis:password', - description: 'Password of your Redis database', - hidden: true, - default: nconf.get('redis:password') || '', - before: function (value) { value = value || nconf.get('redis:password') || ''; return value; }, - }, - { - name: 'redis:database', - description: 'Which database to use (0..n)', - default: nconf.get('redis:database') || 0, - }, - ]; - - module.init = function (callback) { - try { - redis = require('redis'); - } catch (err) { - winston.error('Unable to initialize Redis! Is Redis installed? Error :' + err.message); - process.exit(); - } - - redisClient = module.connect(); - - module.client = redisClient; - - require('./redis/main')(redisClient, module); - require('./redis/hash')(redisClient, module); - require('./redis/sets')(redisClient, module); - require('./redis/sorted')(redisClient, module); - require('./redis/list')(redisClient, module); - - if (typeof callback === 'function') { - callback(); - } - }; - - module.initSessionStore = function (callback) { - var meta = require('../meta'); - var sessionStore = require('connect-redis')(session); - - module.sessionStore = new sessionStore({ - client: module.client, - ttl: meta.getSessionTTLSeconds(), +var _ = require('underscore'); +var winston = require('winston'); +var nconf = require('nconf'); +var semver = require('semver'); +var session = require('express-session'); +var redis = require('redis'); +var redisClient; + +_.mixin(require('underscore.deep')); + +var redisModule = module.exports; + +redisModule.questions = [ + { + name: 'redis:host', + description: 'Host IP or address of your Redis instance', + default: nconf.get('redis:host') || '127.0.0.1', + }, + { + name: 'redis:port', + description: 'Host port of your Redis instance', + default: nconf.get('redis:port') || 6379, + }, + { + name: 'redis:password', + description: 'Password of your Redis database', + hidden: true, + default: nconf.get('redis:password') || '', + before: function (value) { value = value || nconf.get('redis:password') || ''; return value; }, + }, + { + name: 'redis:database', + description: 'Which database to use (0..n)', + default: nconf.get('redis:database') || 0, + }, +]; + +redisModule.init = function (callback) { + redisClient = redisModule.connect(); + + redisModule.client = redisClient; + + require('./redis/main')(redisClient, redisModule); + require('./redis/hash')(redisClient, redisModule); + require('./redis/sets')(redisClient, redisModule); + require('./redis/sorted')(redisClient, redisModule); + require('./redis/list')(redisClient, redisModule); + + if (typeof callback === 'function') { + callback(); + } +}; + +redisModule.initSessionStore = function (callback) { + var meta = require('../meta'); + var sessionStore = require('connect-redis')(session); + + redisModule.sessionStore = new sessionStore({ + client: redisModule.client, + ttl: meta.getSessionTTLSeconds(), + }); + + if (typeof callback === 'function') { + callback(); + } +}; + +redisModule.connect = function (options) { + var redis_socket_or_host = nconf.get('redis:host'); + var cxn; + + if (!redis) { + redis = require('redis'); + } + + options = options || {}; + + if (nconf.get('redis:password')) { + options.auth_pass = nconf.get('redis:password'); + } + + options = _.deepExtend(options, nconf.get('redis:options') || {}); + + if (redis_socket_or_host && redis_socket_or_host.indexOf('/') >= 0) { + /* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */ + cxn = redis.createClient(nconf.get('redis:host'), options); + } else { + /* Else, connect over tcp/ip */ + cxn = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'), options); + } + + cxn.on('error', function (err) { + winston.error(err.stack); + process.exit(1); + }); + + if (nconf.get('redis:password')) { + cxn.auth(nconf.get('redis:password')); + } + + var dbIdx = parseInt(nconf.get('redis:database'), 10); + if (dbIdx) { + cxn.select(dbIdx, function (error) { + if (error) { + winston.error('NodeBB could not connect to your Redis database. Redis returned the following error: ' + error.message); + process.exit(); + } }); + } - if (typeof callback === 'function') { - callback(); - } - }; + return cxn; +}; - module.connect = function (options) { - var redis_socket_or_host = nconf.get('redis:host'); - var cxn; +redisModule.createIndices = function (callback) { + setImmediate(callback); +}; - if (!redis) { - redis = require('redis'); +redisModule.checkCompatibility = function (callback) { + redisModule.info(redisModule.client, function (err, info) { + if (err) { + return callback(err); } - options = options || {}; - if (nconf.get('redis:password')) { - options.auth_pass = nconf.get('redis:password'); + if (semver.lt(info.redis_version, '2.8.9')) { + return callback(new Error('Your Redis version is not new enough to support NodeBB, please upgrade Redis to v2.8.9 or higher.')); } - if (redis_socket_or_host && redis_socket_or_host.indexOf('/') >= 0) { - /* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */ - cxn = redis.createClient(nconf.get('redis:host'), options); - } else { - /* Else, connect over tcp/ip */ - cxn = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'), options); + callback(); + }); +}; + +redisModule.close = function () { + redisClient.quit(); +}; + +redisModule.info = function (cxn, callback) { + if (!cxn) { + return callback(); + } + cxn.info(function (err, data) { + if (err) { + return callback(err); } - cxn.on('error', function (err) { - winston.error(err.stack); - process.exit(1); - }); - - if (nconf.get('redis:password')) { - cxn.auth(nconf.get('redis:password')); - } - - var dbIdx = parseInt(nconf.get('redis:database'), 10); - if (dbIdx) { - cxn.select(dbIdx, function (error) { - if (error) { - winston.error('NodeBB could not connect to your Redis database. Redis returned the following error: ' + error.message); - process.exit(); - } - }); - } - - return cxn; - }; - - module.createIndices = function (callback) { - setImmediate(callback); - }; - - module.checkCompatibility = function (callback) { - module.info(module.client, function (err, info) { - if (err) { - return callback(err); - } - - if (semver.lt(info.redis_version, '2.8.9')) { - return callback(new Error('Your Redis version is not new enough to support NodeBB, please upgrade Redis to v2.8.9 or higher.')); - } - - callback(); - }); - }; - - module.close = function () { - redisClient.quit(); - }; - - module.info = function (cxn, callback) { - if (!cxn) { - return callback(); - } - cxn.info(function (err, data) { - if (err) { - return callback(err); + var lines = data.toString().split('\r\n').sort(); + var redisData = {}; + lines.forEach(function (line) { + var parts = line.split(':'); + if (parts[1]) { + redisData[parts[0]] = parts[1]; } - - var lines = data.toString().split('\r\n').sort(); - var redisData = {}; - lines.forEach(function (line) { - var parts = line.split(':'); - if (parts[1]) { - redisData[parts[0]] = parts[1]; - } - }); - - redisData.raw = JSON.stringify(redisData, null, 4); - redisData.redis = true; - - callback(null, redisData); }); - }; + redisData.used_memory_human = (redisData.used_memory / (1024 * 1024 * 1024)).toFixed(2); + redisData.raw = JSON.stringify(redisData, null, 4); + redisData.redis = true; - module.helpers = module.helpers || {}; - module.helpers.redis = require('./redis/helpers'); -}(exports)); + callback(null, redisData); + }); +}; +redisModule.helpers = redisModule.helpers || {}; +redisModule.helpers.redis = require('./redis/helpers'); diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 61d83a93f6..9dcb005528 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -8,11 +8,17 @@ module.exports = function (redisClient, module) { if (!key || !data) { return callback(); } + + if (data.hasOwnProperty('')) { + delete data['']; + } + Object.keys(data).forEach(function (key) { if (data[key] === undefined) { delete data[key]; } }); + redisClient.hmset(key, data, function (err) { callback(err); }); diff --git a/src/database/redis/helpers.js b/src/database/redis/helpers.js index 7100437177..b1c2a98092 100644 --- a/src/database/redis/helpers.js +++ b/src/database/redis/helpers.js @@ -1,6 +1,6 @@ 'use strict'; -var helpers = {}; +var helpers = module.exports; helpers.multiKeys = function (redisClient, command, keys, callback) { callback = callback || function () {}; @@ -15,7 +15,7 @@ helpers.multiKeysValue = function (redisClient, command, keys, value, callback) callback = callback || function () {}; var multi = redisClient.multi(); for (var i = 0; i < keys.length; i += 1) { - multi[command](keys[i], value); + multi[command](String(keys[i]), String(value)); } multi.exec(callback); }; @@ -24,7 +24,7 @@ helpers.multiKeyValues = function (redisClient, command, key, values, callback) callback = callback || function () {}; var multi = redisClient.multi(); for (var i = 0; i < values.length; i += 1) { - multi[command](key, values[i]); + multi[command](String(key), String(values[i])); } multi.exec(callback); }; @@ -35,5 +35,3 @@ helpers.resultsToBool = function (results) { } return results; }; - -module.exports = helpers; diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index 67f55bf343..9bb004550a 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -124,8 +124,12 @@ module.exports = function (redisClient, module) { }; module.sortedSetScore = function (key, value, callback) { + if (!key || value === undefined) { + return callback(null, null); + } + redisClient.zscore(key, value, function (err, score) { - callback(err, !err ? parseFloat(score) : undefined); + callback(err, !err ? parseFloat(score) : null); }); }; diff --git a/src/database/redis/sorted/add.js b/src/database/redis/sorted/add.js index e60d079eac..b279f5ba76 100644 --- a/src/database/redis/sorted/add.js +++ b/src/database/redis/sorted/add.js @@ -3,6 +3,9 @@ module.exports = function (redisClient, module) { module.sortedSetAdd = function (key, score, value, callback) { callback = callback || function () {}; + if (!key) { + return setImmediate(callback); + } if (Array.isArray(score) && Array.isArray(value)) { return sortedSetAddMulti(key, score, value, callback); } @@ -33,10 +36,15 @@ module.exports = function (redisClient, module) { module.sortedSetsAdd = function (keys, score, value, callback) { callback = callback || function () {}; + if (!Array.isArray(keys) || !keys.length) { + return callback(); + } var multi = redisClient.multi(); for (var i = 0; i < keys.length; i += 1) { - multi.zadd(keys[i], score, value); + if (keys[i]) { + multi.zadd(keys[i], score, value); + } } multi.exec(function (err) { diff --git a/src/file.js b/src/file.js index 5a1e65a4b2..35b57d9113 100644 --- a/src/file.js +++ b/src/file.js @@ -81,7 +81,7 @@ file.allowedExtensions = function () { if (!extension.startsWith('.')) { extension = '.' + extension; } - return extension; + return extension.toLowerCase(); }); if (allowedExtensions.indexOf('.jpg') !== -1 && allowedExtensions.indexOf('.jpeg') === -1) { @@ -92,20 +92,28 @@ file.allowedExtensions = function () { }; file.exists = function (path, callback) { - fs.stat(path, function (err, stat) { - callback(!err && stat); + fs.stat(path, function (err) { + if (err) { + if (err.code === 'ENOENT') { + return callback(null, false); + } + return callback(err); + } + return callback(null, true); }); }; file.existsSync = function (path) { - var exists = false; try { - exists = fs.statSync(path); + fs.statSync(path); } catch (err) { - exists = false; + if (err.code === 'ENOENT') { + return false; + } + throw err; } - return !!exists; + return true; }; file.link = function link(filePath, destPath, cb) { diff --git a/src/flags.js b/src/flags.js new file mode 100644 index 0000000000..9022af2c45 --- /dev/null +++ b/src/flags.js @@ -0,0 +1,686 @@ +'use strict'; + +var async = require('async'); +var db = require('./database'); +var user = require('./user'); +var groups = require('./groups'); +var meta = require('./meta'); +var notifications = require('./notifications'); +var analytics = require('./analytics'); +var topics = require('./topics'); +var posts = require('./posts'); +var privileges = require('./privileges'); +var plugins = require('./plugins'); +var utils = require('../public/src/utils'); +var _ = require('underscore'); +var S = require('string'); + +var Flags = {}; + +Flags.get = function (flagId, callback) { + async.waterfall([ + // First stage + async.apply(async.parallel, { + base: async.apply(db.getObject.bind(db), 'flag:' + flagId), + history: async.apply(Flags.getHistory, flagId), + notes: async.apply(Flags.getNotes, flagId), + }), + function (data, next) { + // Second stage + async.parallel({ + userObj: async.apply(user.getUserFields, data.base.uid, ['username', 'userslug', 'picture']), + targetObj: async.apply(Flags.getTarget, data.base.type, data.base.targetId, data.base.uid), + }, function (err, payload) { + // Final object return construction + next(err, Object.assign(data.base, { + datetimeISO: new Date(parseInt(data.base.datetime, 10)).toISOString(), + target_readable: data.base.type.charAt(0).toUpperCase() + data.base.type.slice(1) + ' ' + data.base.targetId, + target: payload.targetObj, + history: data.history, + notes: data.notes, + reporter: payload.userObj, + })); + }); + }, + ], callback); +}; + +Flags.list = function (filters, uid, callback) { + if (typeof filters === 'function' && !uid && !callback) { + callback = filters; + filters = {}; + } + + var sets = []; + var orSets = []; + var prepareSets = function (setPrefix, value) { + if (!Array.isArray(value)) { + sets.push(setPrefix + value); + } else if (value.length) { + value.forEach(function (x) { + orSets.push(setPrefix + x); + }); + } + }; + + if (Object.keys(filters).length > 0) { + for (var type in filters) { + if (filters.hasOwnProperty(type)) { + switch (type) { + case 'type': + prepareSets('flags:byType:', filters[type]); + break; + + case 'state': + prepareSets('flags:byState:', filters[type]); + break; + + case 'reporterId': + prepareSets('flags:byReporter:', filters[type]); + break; + + case 'assignee': + prepareSets('flags:byAssignee:', filters[type]); + break; + + case 'targetUid': + prepareSets('flags:byTargetUid:', filters[type]); + break; + + case 'cid': + prepareSets('flags:byCid:', filters[type]); + break; + + case 'quick': + switch (filters.quick) { + case 'mine': + sets.push('flags:byAssignee:' + uid); + break; + } + break; + } + } + } + } + sets = (sets.length || orSets.length) ? sets : ['flags:datetime']; // No filter default + + async.waterfall([ + function (next) { + if (sets.length === 1) { + db.getSortedSetRevRange(sets[0], 0, -1, next); + } else if (sets.length > 1) { + db.getSortedSetRevIntersect({ sets: sets, start: 0, stop: -1, aggregate: 'MAX' }, next); + } else { + next(null, []); + } + }, + function (flagIds, next) { + // Find flags according to "or" rules, if any + if (orSets.length) { + db.getSortedSetRevUnion({ sets: orSets, start: 0, stop: -1, aggregate: 'MAX' }, function (err, _flagIds) { + if (err) { + return next(err); + } + + if (sets.length) { + // If flag ids are already present, return a subset of flags that are in both sets + next(null, _.intersection(flagIds, _flagIds)); + } else { + // Otherwise, return all flags returned via orSets + next(null, _.union(flagIds, _flagIds)); + } + }); + } else { + setImmediate(next, null, flagIds); + } + }, + function (flagIds, next) { + async.map(flagIds, function (flagId, next) { + async.waterfall([ + async.apply(db.getObject, 'flag:' + flagId), + function (flagObj, next) { + user.getUserFields(flagObj.uid, ['username', 'picture'], function (err, userObj) { + next(err, Object.assign(flagObj, { + reporter: { + username: userObj.username, + picture: userObj.picture, + 'icon:bgColor': userObj['icon:bgColor'], + 'icon:text': userObj['icon:text'], + }, + })); + }); + }, + ], function (err, flagObj) { + if (err) { + return next(err); + } + + switch (flagObj.state) { + case 'open': + flagObj.labelClass = 'info'; + break; + case 'wip': + flagObj.labelClass = 'warning'; + break; + case 'resolved': + flagObj.labelClass = 'success'; + break; + case 'rejected': + flagObj.labelClass = 'danger'; + break; + } + + next(null, Object.assign(flagObj, { + target_readable: flagObj.type.charAt(0).toUpperCase() + flagObj.type.slice(1) + ' ' + flagObj.targetId, + datetimeISO: new Date(parseInt(flagObj.datetime, 10)).toISOString(), + })); + }); + }, next); + }, + ], callback); +}; + +Flags.validate = function (payload, callback) { + async.parallel({ + targetExists: async.apply(Flags.targetExists, payload.type, payload.id), + target: async.apply(Flags.getTarget, payload.type, payload.id, payload.uid), + reporter: async.apply(user.getUserData, payload.uid), + }, function (err, data) { + if (err) { + return callback(err); + } + + if (data.target.deleted) { + return callback(new Error('[[error:post-deleted]]')); + } else if (parseInt(data.reporter.banned, 10)) { + return callback(new Error('[[error:user-banned]]')); + } + + switch (payload.type) { + case 'post': + privileges.posts.canEdit(payload.id, payload.uid, function (err, editable) { + if (err) { + return callback(err); + } + + var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1; + // Check if reporter meets rep threshold (or can edit the target post, in which case threshold does not apply) + if (!editable.flag && parseInt(data.reporter.reputation, 10) < minimumReputation) { + return callback(new Error('[[error:not-enough-reputation-to-flag]]')); + } + + callback(); + }); + break; + + case 'user': + privileges.users.canEdit(payload.uid, payload.id, function (err, editable) { + if (err) { + return callback(err); + } + + var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1; + // Check if reporter meets rep threshold (or can edit the target user, in which case threshold does not apply) + if (!editable && parseInt(data.reporter.reputation, 10) < minimumReputation) { + return callback(new Error('[[error:not-enough-reputation-to-flag]]')); + } + + callback(); + }); + break; + + default: + callback(new Error('[[error:invalid-data]]')); + break; + } + }); +}; + +Flags.getNotes = function (flagId, callback) { + async.waterfall([ + async.apply(db.getSortedSetRevRangeWithScores.bind(db), 'flag:' + flagId + ':notes', 0, -1), + function (notes, next) { + var uids = []; + var noteObj; + notes = notes.map(function (note) { + try { + noteObj = JSON.parse(note.value); + uids.push(noteObj[0]); + return { + uid: noteObj[0], + content: noteObj[1], + datetime: note.score, + datetimeISO: new Date(parseInt(note.score, 10)).toISOString(), + }; + } catch (e) { + return next(e); + } + }); + next(null, notes, uids); + }, + function (notes, uids, next) { + user.getUsersFields(uids, ['username', 'userslug', 'picture'], function (err, users) { + if (err) { + return next(err); + } + + next(null, notes.map(function (note, idx) { + note.user = users[idx]; + return note; + })); + }); + }, + ], callback); +}; + +Flags.create = function (type, id, uid, reason, timestamp, callback) { + var targetUid; + var targetCid; + var doHistoryAppend = false; + + // timestamp is optional + if (typeof timestamp === 'function' && !callback) { + callback = timestamp; + timestamp = Date.now(); + doHistoryAppend = true; + } + + async.waterfall([ + function (next) { + async.parallel([ + // Sanity checks + async.apply(Flags.exists, type, id, uid), + async.apply(Flags.targetExists, type, id), + + // Extra data for zset insertion + async.apply(Flags.getTargetUid, type, id), + async.apply(Flags.getTargetCid, type, id), + ], function (err, checks) { + if (err) { + return next(err); + } + + targetUid = checks[2] || null; + targetCid = checks[3] || null; + + if (checks[0]) { + return next(new Error('[[error:already-flagged]]')); + } else if (!checks[1]) { + return next(new Error('[[error:invalid-data]]')); + } + next(); + }); + }, + async.apply(db.incrObjectField, 'global', 'nextFlagId'), + function (flagId, next) { + var tasks = [ + async.apply(db.setObject.bind(db), 'flag:' + flagId, { + flagId: flagId, + type: type, + targetId: id, + description: reason, + uid: uid, + datetime: timestamp, + }), + async.apply(db.sortedSetAdd.bind(db), 'flags:datetime', timestamp, flagId), // by time, the default + async.apply(db.sortedSetAdd.bind(db), 'flags:byReporter:' + uid, timestamp, flagId), // by reporter + async.apply(db.sortedSetAdd.bind(db), 'flags:byType:' + type, timestamp, flagId), // by flag type + async.apply(db.sortedSetAdd.bind(db), 'flags:hash', flagId, [type, id, uid].join(':')), // save zset for duplicate checking + async.apply(analytics.increment, 'flags'), // some fancy analytics + ]; + + if (targetUid) { + tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byTargetUid:' + targetUid, timestamp, flagId)); // by target uid + } + if (targetCid) { + tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byCid:' + targetCid, timestamp, flagId)); // by target cid + } + if (type === 'post') { + tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byPid:' + id, timestamp, flagId)); // by target pid + if (targetUid) { + tasks.push(async.apply(db.sortedSetIncrBy.bind(db), 'users:flags', 1, targetUid)); + } + } + + async.parallel(tasks, function (err) { + if (err) { + return next(err); + } + + if (doHistoryAppend) { + Flags.update(flagId, uid, { state: 'open' }); + } + + next(null, flagId); + }); + }, + async.apply(Flags.get), + ], callback); +}; + +Flags.exists = function (type, id, uid, callback) { + db.isSortedSetMember('flags:hash', [type, id, uid].join(':'), callback); +}; + +Flags.getTarget = function (type, id, uid, callback) { + async.waterfall([ + async.apply(Flags.targetExists, type, id), + function (exists, next) { + if (exists) { + switch (type) { + case 'post': + async.waterfall([ + async.apply(posts.getPostsByPids, [id], uid), + function (posts, next) { + topics.addPostData(posts, uid, next); + }, + ], function (err, posts) { + next(err, posts[0]); + }); + break; + + case 'user': + user.getUsersData([id], function (err, users) { + next(err, users ? users[0] : undefined); + }); + break; + + default: + next(new Error('[[error:invalid-data]]')); + break; + } + } else { + // Target used to exist (otherwise flag creation'd fail), but no longer + next(null, {}); + } + }, + ], callback); +}; + +Flags.targetExists = function (type, id, callback) { + switch (type) { + case 'post': + posts.exists(id, callback); + break; + + case 'user': + user.exists(id, callback); + break; + + default: + callback(new Error('[[error:invalid-data]]')); + break; + } +}; + +Flags.getTargetUid = function (type, id, callback) { + switch (type) { + case 'post': + posts.getPostField(id, 'uid', callback); + break; + + default: + setImmediate(callback, null, id); + break; + } +}; + +Flags.getTargetCid = function (type, id, callback) { + switch (type) { + case 'post': + posts.getCidByPid(id, callback); + break; + + default: + setImmediate(callback, null, id); + break; + } +}; + +Flags.update = function (flagId, uid, changeset, callback) { + // Retrieve existing flag data to compare for history-saving purposes + var fields = ['state', 'assignee']; + var tasks = []; + var now = changeset.datetime || Date.now(); + var notifyAssignee = function (assigneeId, next) { + if (assigneeId === '') { + // Do nothing + return next(); + } + // Notify assignee of this update + notifications.create({ + type: 'my-flags', + bodyShort: '[[notifications:flag_assigned_to_you, ' + flagId + ']]', + bodyLong: '', + path: '/flags/' + flagId, + nid: 'flags:assign:' + flagId + ':uid:' + assigneeId, + from: uid, + }, function (err, notification) { + if (err || !notification) { + return next(err); + } + + notifications.push(notification, [assigneeId], next); + }); + }; + + async.waterfall([ + async.apply(db.getObjectFields.bind(db), 'flag:' + flagId, fields), + function (current, next) { + for (var prop in changeset) { + if (changeset.hasOwnProperty(prop)) { + if (current[prop] === changeset[prop]) { + delete changeset[prop]; + } else { + // Add tasks as necessary + switch (prop) { + case 'state': + tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byState:' + changeset[prop], now, flagId)); + tasks.push(async.apply(db.sortedSetRemove.bind(db), 'flags:byState:' + current[prop], flagId)); + break; + + case 'assignee': + tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byAssignee:' + changeset[prop], now, flagId)); + tasks.push(async.apply(notifyAssignee, changeset[prop])); + break; + } + } + } + } + + if (!Object.keys(changeset).length) { + // No changes + return next(); + } + + // Save new object to db (upsert) + tasks.push(async.apply(db.setObject, 'flag:' + flagId, changeset)); + + // Append history + tasks.push(async.apply(Flags.appendHistory, flagId, uid, changeset)); + + // Fire plugin hook + tasks.push(async.apply(plugins.fireHook, 'action:flag.update', { flagId: flagId, changeset: changeset, uid: uid })); + + async.parallel(tasks, function (err) { + return next(err); + }); + }, + ], callback); +}; + +Flags.getHistory = function (flagId, callback) { + var history; + var uids = []; + async.waterfall([ + async.apply(db.getSortedSetRevRangeWithScores.bind(db), 'flag:' + flagId + ':history', 0, -1), + function (_history, next) { + history = _history.map(function (entry) { + try { + entry.value = JSON.parse(entry.value); + } catch (e) { + return callback(e); + } + + uids.push(entry.value[0]); + + // Deserialise changeset + var changeset = entry.value[1]; + if (changeset.hasOwnProperty('state')) { + changeset.state = changeset.state === undefined ? '' : '[[flags:state-' + changeset.state + ']]'; + } + + return { + uid: entry.value[0], + fields: changeset, + datetime: entry.score, + datetimeISO: new Date(parseInt(entry.score, 10)).toISOString(), + }; + }); + + user.getUsersFields(uids, ['username', 'userslug', 'picture'], next); + }, + ], function (err, users) { + if (err) { + return callback(err); + } + + // Append user data to each history event + history = history.map(function (event, idx) { + event.user = users[idx]; + return event; + }); + + callback(null, history); + }); +}; + +Flags.appendHistory = function (flagId, uid, changeset, callback) { + var payload; + var datetime = changeset.datetime || Date.now(); + delete changeset.datetime; + + try { + payload = JSON.stringify([uid, changeset, datetime]); + } catch (e) { + return callback(e); + } + + db.sortedSetAdd('flag:' + flagId + ':history', datetime, payload, callback); +}; + +Flags.appendNote = function (flagId, uid, note, datetime, callback) { + if (typeof datetime === 'function' && !callback) { + callback = datetime; + datetime = Date.now(); + } + + var payload; + try { + payload = JSON.stringify([uid, note]); + } catch (e) { + return callback(e); + } + + async.waterfall([ + async.apply(db.sortedSetAdd, 'flag:' + flagId + ':notes', datetime, payload), + async.apply(Flags.appendHistory, flagId, uid, { + notes: null, + datetime: datetime, + }), + ], callback); +}; + +Flags.notify = function (flagObj, uid, callback) { + // Notify administrators, mods, and other associated people + if (!callback) { + callback = function () {}; + } + + switch (flagObj.type) { + case 'post': + async.parallel({ + post: function (next) { + async.waterfall([ + async.apply(posts.getPostData, flagObj.targetId), + async.apply(posts.parsePost), + ], next); + }, + title: async.apply(topics.getTitleByPid, flagObj.targetId), + admins: async.apply(groups.getMembers, 'administrators', 0, -1), + globalMods: async.apply(groups.getMembers, 'Global Moderators', 0, -1), + moderators: function (next) { + async.waterfall([ + async.apply(posts.getCidByPid, flagObj.targetId), + function (cid, next) { + groups.getMembers('cid:' + cid + ':privileges:mods', 0, -1, next); + }, + ], next); + }, + }, function (err, results) { + if (err) { + return callback(err); + } + + var title = S(results.title).decodeHTMLEntities().s; + var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); + + notifications.create({ + type: 'new-post-flag', + bodyShort: '[[notifications:user_flagged_post_in, ' + flagObj.reporter.username + ', ' + titleEscaped + ']]', + bodyLong: flagObj.description, + pid: flagObj.targetId, + path: '/post/' + flagObj.targetId, + nid: 'flag:post:' + flagObj.targetId + ':uid:' + uid, + from: uid, + mergeId: 'notifications:user_flagged_post_in|' + flagObj.targetId, + topicTitle: results.title, + }, function (err, notification) { + if (err || !notification) { + return callback(err); + } + + plugins.fireHook('action:flag.create', { + flag: flagObj, + }); + notifications.push(notification, results.admins.concat(results.moderators).concat(results.globalMods), callback); + }); + }); + break; + + case 'user': + async.parallel({ + admins: async.apply(groups.getMembers, 'administrators', 0, -1), + globalMods: async.apply(groups.getMembers, 'Global Moderators', 0, -1), + }, function (err, results) { + if (err) { + return callback(err); + } + + notifications.create({ + bodyShort: '[[notifications:user_flagged_user, ' + flagObj.reporter.username + ', ' + flagObj.target.username + ']]', + bodyLong: flagObj.description, + path: '/uid/' + flagObj.targetId, + nid: 'flag:user:' + flagObj.targetId + ':uid:' + uid, + from: uid, + mergeId: 'notifications:user_flagged_user|' + flagObj.targetId, + }, function (err, notification) { + if (err || !notification) { + return callback(err); + } + + plugins.fireHook('action:flag.create', { + flag: flagObj, + }); + notifications.push(notification, results.admins.concat(results.globalMods), callback); + }); + }); + break; + + default: + callback(new Error('[[error:invalid-data]]')); + break; + } +}; + +module.exports = Flags; diff --git a/src/groups/cover.js b/src/groups/cover.js index a7d7550fa0..99386879c1 100644 --- a/src/groups/cover.js +++ b/src/groups/cover.js @@ -2,6 +2,7 @@ var async = require('async'); var fs = require('fs'); +var path = require('path'); var Jimp = require('jimp'); var mime = require('mime'); var winston = require('winston'); @@ -37,8 +38,9 @@ module.exports = function (Groups) { }, function (_tempPath, next) { tempPath = _tempPath; + uploadsController.uploadGroupCover(uid, { - name: 'groupCover', + name: 'groupCover' + path.extname(tempPath), path: tempPath, type: type, }, next); @@ -52,7 +54,7 @@ module.exports = function (Groups) { }, function (next) { uploadsController.uploadGroupCover(uid, { - name: 'groupCoverThumb', + name: 'groupCoverThumb' + path.extname(tempPath), path: tempPath, type: type, }, next); diff --git a/src/groups/create.js b/src/groups/create.js index a14441608f..c720923ca7 100644 --- a/src/groups/create.js +++ b/src/groups/create.js @@ -69,7 +69,7 @@ module.exports = function (Groups) { async.series(tasks, next); }, function (results, next) { - plugins.fireHook('action:group.create', groupData); + plugins.fireHook('action:group.create', { group: groupData }); next(null, groupData); }, ], callback); diff --git a/src/groups/delete.js b/src/groups/delete.js index e05eff6370..346b2e1ef5 100644 --- a/src/groups/delete.js +++ b/src/groups/delete.js @@ -4,6 +4,7 @@ var async = require('async'); var plugins = require('../plugins'); var utils = require('../utils'); var db = require('./../database'); +var batch = require('../batch'); module.exports = function (Groups) { Groups.destroy = function (groupName, callback) { @@ -16,8 +17,6 @@ module.exports = function (Groups) { } var groupObj = groupsData[0]; - plugins.fireHook('action:group.destroy', groupObj); - async.parallel([ async.apply(db.delete, 'group:' + groupName), async.apply(db.sortedSetRemove, 'groups:createtime', groupName), @@ -28,22 +27,24 @@ module.exports = function (Groups) { async.apply(db.delete, 'group:' + groupName + ':pending'), async.apply(db.delete, 'group:' + groupName + ':invited'), async.apply(db.delete, 'group:' + groupName + ':owners'), + async.apply(db.delete, 'group:' + groupName + ':member:pids'), async.apply(db.deleteObjectField, 'groupslug:groupname', utils.slugify(groupName)), function (next) { - db.getSortedSetRange('groups:createtime', 0, -1, function (err, groups) { - if (err) { - return next(err); - } - async.each(groups, function (group, next) { - db.sortedSetRemove('group:' + group + ':members', groupName, next); - }, next); - }); + batch.processSortedSet('groups:createtime', function (groupNames, next) { + var keys = groupNames.map(function (group) { + return 'group:' + group + ':members'; + }); + db.sortedSetsRemove(keys, groupName, next); + }, { + batch: 500, + }, next); }, ], function (err) { if (err) { return callback(err); } Groups.resetCache(); + plugins.fireHook('action:group.destroy', { group: groupObj }); callback(); }); }); diff --git a/src/groups/posts.js b/src/groups/posts.js index 6f4d65520d..bd829f3d58 100644 --- a/src/groups/posts.js +++ b/src/groups/posts.js @@ -7,21 +7,61 @@ var privileges = require('../privileges'); var posts = require('../posts'); module.exports = function (Groups) { - Groups.getLatestMemberPosts = function (groupName, max, uid, callback) { + Groups.onNewPostMade = function (postData, callback) { + if (!parseInt(postData.uid, 10)) { + return setImmediate(callback); + } + + var groupNames; async.waterfall([ function (next) { - Groups.getMembers(groupName, 0, -1, next); + Groups.getUserGroupMembership('groups:visible:createtime', [postData.uid], next); }, - function (uids, next) { - if (!Array.isArray(uids) || !uids.length) { - return callback(null, []); - } - var keys = uids.map(function (uid) { - return 'uid:' + uid + ':posts'; + function (_groupNames, next) { + groupNames = _groupNames[0]; + + var keys = groupNames.map(function (groupName) { + return 'group:' + groupName + ':member:pids'; }); - db.getSortedSetRevRange(keys, 0, max - 1, next); + + db.sortedSetsAdd(keys, postData.timestamp, postData.pid, next); + }, + function (next) { + async.each(groupNames, function (groupName, next) { + truncateMemberPosts(groupName, next); + }, next); + }, + ], callback); + }; + + function truncateMemberPosts(groupName, callback) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange('group:' + groupName + ':member:pids', 10, 10, next); + }, + function (lastPid, next) { + lastPid = lastPid[0]; + if (!parseInt(lastPid, 10)) { + return callback(); + } + db.sortedSetScore('group:' + groupName + ':member:pids', lastPid, next); + }, + function (score, next) { + db.sortedSetsRemoveRangeByScore(['group:' + groupName + ':member:pids'], '-inf', score, next); + }, + ], callback); + } + + Groups.getLatestMemberPosts = function (groupName, max, uid, callback) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange('group:' + groupName + ':member:pids', 0, max - 1, next); }, function (pids, next) { + if (!Array.isArray(pids) || !pids.length) { + return callback(null, []); + } + privileges.posts.filter('read', pids, uid, next); }, function (pids, next) { diff --git a/src/groups/user.js b/src/groups/user.js index 9a68478ade..fb524a87b9 100644 --- a/src/groups/user.js +++ b/src/groups/user.js @@ -22,6 +22,19 @@ module.exports = function (Groups) { }; Groups.getUserGroupsFromSet = function (set, uids, callback) { + async.waterfall([ + function (next) { + Groups.getUserGroupMembership(set, uids, next); + }, + function (memberOf, next) { + async.map(memberOf, function (memberOf, next) { + Groups.getGroupsData(memberOf, next); + }, next); + }, + ], callback); + }; + + Groups.getUserGroupMembership = function (set, uids, callback) { async.waterfall([ function (next) { db.getSortedSetRevRange(set, 0, -1, next); @@ -40,7 +53,7 @@ module.exports = function (Groups) { } }); - Groups.getGroupsData(memberOf, next); + next(null, memberOf); }, ], next); }, next); diff --git a/src/install.js b/src/install.js index 50e8328682..7ba5e6ccdb 100644 --- a/src/install.js +++ b/src/install.js @@ -507,12 +507,11 @@ install.setup = function (callback) { setCopyrightWidget, function (next) { var upgrade = require('./upgrade'); - upgrade.check(function (err, uptodate) { - if (err) { + upgrade.check(function (err) { + if (err && err.message === 'schema-out-of-date') { + upgrade.run(next); + } else if (err) { return next(err); - } - if (!uptodate) { - upgrade.upgrade(next); } else { next(); } diff --git a/src/languages.js b/src/languages.js index 520ae8bba1..4414562e11 100644 --- a/src/languages.js +++ b/src/languages.js @@ -89,7 +89,7 @@ Languages.list = function (callback) { // filter out invalid ones languages = languages.filter(function (lang) { - return lang.code && lang.name && lang.dir; + return lang && lang.code && lang.name && lang.dir; }); listCache = languages; diff --git a/src/messaging.js b/src/messaging.js index 8864368c11..0ad092cf5e 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -102,7 +102,7 @@ Messaging.isNewSet = function (uid, roomId, timestamp, callback) { }, function (messages, next) { if (messages && messages.length) { - next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + (1000 * 60 * 5)); + next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + Messaging.newMessageCutoff); } else { next(null, true); } diff --git a/src/messaging/create.js b/src/messaging/create.js index 2c6ac84ffb..f4fe421eac 100644 --- a/src/messaging/create.js +++ b/src/messaging/create.js @@ -30,9 +30,11 @@ module.exports = function (Messaging) { if (!content) { return callback(new Error('[[error:invalid-chat-message]]')); } + content = String(content); - if (content.length > (meta.config.maximumChatMessageLength || 1000)) { - return callback(new Error('[[error:chat-message-too-long]]')); + var maximumChatMessageLength = (meta.config.maximumChatMessageLength || 1000); + if (content.length > maximumChatMessageLength) { + return callback(new Error('[[error:chat-message-too-long, ' + maximumChatMessageLength + ']]')); } callback(); }; @@ -52,7 +54,7 @@ module.exports = function (Messaging) { function (_mid, next) { mid = _mid; message = { - content: content, + content: String(content), timestamp: timestamp, fromuid: fromuid, roomId: roomId, diff --git a/src/messaging/data.js b/src/messaging/data.js index b565294902..b3a2ba58dd 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -9,6 +9,8 @@ var utils = require('../utils'); var plugins = require('../plugins'); module.exports = function (Messaging) { + Messaging.newMessageCutoff = 1000 * 60 * 3; + Messaging.getMessageField = function (mid, field, callback) { Messaging.getMessageFields(mid, [field], function (err, fields) { callback(err, fields ? fields[field] : null); @@ -81,7 +83,7 @@ module.exports = function (Messaging) { // Add a spacer in between messages with time gaps between them messages = messages.map(function (message, index) { // Compare timestamps with the previous message, and check if a spacer needs to be added - if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index - 1].timestamp, 10) + (1000 * 60 * 5)) { + if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index - 1].timestamp, 10) + Messaging.newMessageCutoff) { // If it's been 5 minutes, this is a new set of messages message.newSet = true; } else if (index > 0 && message.fromuid !== messages[index - 1].fromuid) { @@ -116,7 +118,7 @@ module.exports = function (Messaging) { } if ( - (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + (1000 * 60 * 5)) || + (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + Messaging.newMessageCutoff) || (parseInt(messages[0].fromuid, 10) !== parseInt(fields.fromuid, 10)) ) { // If it's been 5 minutes, this is a new set of messages diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js index 37318121c3..5d433b2c33 100644 --- a/src/messaging/notifications.js +++ b/src/messaging/notifications.js @@ -79,6 +79,7 @@ module.exports = function (Messaging) { } notifications.create({ + type: 'new-chat', bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]', bodyLong: messageObj.content, nid: 'chat_' + fromuid + '_' + roomId, diff --git a/src/meta/dependencies.js b/src/meta/dependencies.js index 939f14b674..034061f636 100644 --- a/src/meta/dependencies.js +++ b/src/meta/dependencies.js @@ -19,14 +19,17 @@ module.exports = function (Meta) { winston.verbose('Checking dependencies for outdated modules'); - async.every(modules, function (module, next) { + async.each(modules, function (module, next) { fs.readFile(path.join(__dirname, '../../node_modules/', module, 'package.json'), { encoding: 'utf-8', }, function (err, pkgData) { - // If a bundled plugin/theme is not present, skip the dep check (#3384) - if (err && err.code === 'ENOENT' && (module === 'nodebb-rewards-essentials' || module.startsWith('nodebb-plugin') || module.startsWith('nodebb-theme'))) { - winston.warn('[meta/dependencies] Bundled plugin ' + module + ' not found, skipping dependency check.'); - return next(true); + if (err) { + // If a bundled plugin/theme is not present, skip the dep check (#3384) + if (err.code === 'ENOENT' && (module === 'nodebb-rewards-essentials' || module.startsWith('nodebb-plugin') || module.startsWith('nodebb-theme'))) { + winston.warn('[meta/dependencies] Bundled plugin ' + module + ' not found, skipping dependency check.'); + return next(); + } + return next(err); } try { @@ -34,20 +37,24 @@ module.exports = function (Meta) { } catch (e) { process.stdout.write('[' + 'missing'.red + '] ' + module.bold + ' is a required dependency but could not be found\n'); depsMissing = true; - return next(true); + return next(); } var ok = !semver.validRange(pkg.dependencies[module]) || semver.satisfies(pkgData.version, pkg.dependencies[module]); if (ok || (pkgData._resolved && pkgData._resolved.indexOf('//github.com') !== -1)) { - next(true); + next(); } else { process.stdout.write('[' + 'outdated'.yellow + '] ' + module.bold + ' installed v' + pkgData.version + ', package.json requires ' + pkg.dependencies[module] + '\n'); depsOutdated = true; - next(true); + next(); } }); - }, function () { + }, function (err) { + if (err) { + return callback(err); + } + if (depsMissing) { callback(new Error('dependencies-missing')); } else if (depsOutdated) { diff --git a/src/meta/errors.js b/src/meta/errors.js index fb169764ba..f269490bbf 100644 --- a/src/meta/errors.js +++ b/src/meta/errors.js @@ -1,7 +1,9 @@ 'use strict'; var async = require('async'); +var winston = require('winston'); var validator = require('validator'); +var cronJob = require('cron').CronJob; var db = require('../database'); var analytics = require('../analytics'); @@ -9,17 +11,45 @@ var analytics = require('../analytics'); module.exports = function (Meta) { Meta.errors = {}; + var counters = {}; + + new cronJob('0 * * * * *', function () { + Meta.errors.writeData(); + }, null, true); + + Meta.errors.writeData = function () { + var dbQueue = []; + if (Object.keys(counters).length > 0) { + for (var key in counters) { + if (counters.hasOwnProperty(key)) { + dbQueue.push(async.apply(db.sortedSetIncrBy, 'errors:404', counters[key], key)); + } + } + counters = {}; + async.series(dbQueue, function (err) { + if (err) { + winston.error(err); + } + }); + } + }; + Meta.errors.log404 = function (route, callback) { callback = callback || function () {}; + if (!route) { + return setImmediate(callback); + } route = route.replace(/\/$/, ''); // remove trailing slashes analytics.increment('errors:404'); - db.sortedSetIncrBy('errors:404', 1, route, callback); + counters[route] = counters[route] || 0; + counters[route] += 1; + setImmediate(callback); }; Meta.errors.get = function (escape, callback) { async.waterfall([ function (next) { - db.getSortedSetRevRangeWithScores('errors:404', 0, -1, next); + db.getSortedSetRevRangeWithScores('errors:404', 0, 199, next); }, function (data, next) { data = data.map(function (nfObject) { diff --git a/src/meta/js.js b/src/meta/js.js index 967b100a32..f654d45644 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -54,7 +54,6 @@ module.exports = function (Meta) { 'public/src/client/unread.js', 'public/src/client/topic.js', 'public/src/client/topic/events.js', - 'public/src/client/topic/flag.js', 'public/src/client/topic/fork.js', 'public/src/client/topic/move.js', 'public/src/client/topic/posts.js', @@ -78,6 +77,8 @@ module.exports = function (Meta) { 'public/src/modules/taskbar.js', 'public/src/modules/helpers.js', 'public/src/modules/string.js', + 'public/src/modules/flags.js', + 'public/src/modules/storage.js', ], // modules listed below are built (/src/modules) so they can be defined anonymously diff --git a/src/meta/languages.js b/src/meta/languages.js index 395711cba7..90c3603677 100644 --- a/src/meta/languages.js +++ b/src/meta/languages.js @@ -27,9 +27,7 @@ function getTranslationTree(callback) { }); // Filter out plugins with invalid paths - async.filter(paths, file.exists, function (paths) { - next(null, paths); - }); + async.filter(paths, file.exists, next); }, function (paths, next) { async.map(paths, Plugins.loadPluginInfo, next); diff --git a/src/meta/logs.js b/src/meta/logs.js index e85c0a5e11..4df96261cc 100644 --- a/src/meta/logs.js +++ b/src/meta/logs.js @@ -1,13 +1,12 @@ 'use strict'; var path = require('path'); -var nconf = require('nconf'); var fs = require('fs'); var winston = require('winston'); module.exports = function (Meta) { Meta.logs = { - path: path.join(nconf.get('base_dir'), 'logs', 'output.log'), + path: path.join(__dirname, '..', '..', 'logs', 'output.log'), }; Meta.logs.get = function (callback) { diff --git a/src/meta/minifier.js b/src/meta/minifier.js index 6152112c59..43761c9d33 100644 --- a/src/meta/minifier.js +++ b/src/meta/minifier.js @@ -16,13 +16,21 @@ Minifier.js.minify = function (scripts, minify, callback) { }); async.filter(scripts, function (script, next) { - file.exists(script, function (exists) { + file.exists(script, function (err, exists) { + if (err) { + return next(err); + } + if (!exists) { console.warn('[minifier] file not found, ' + script); } - next(exists); + next(null, exists); }); - }, function (scripts) { + }, function (err, scripts) { + if (err) { + return callback(err); + } + if (minify) { minifyScripts(scripts, callback); } else { diff --git a/src/meta/tags.js b/src/meta/tags.js index 5b1097d427..ac0b395a23 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -9,7 +9,7 @@ var plugins = require('../plugins'); module.exports = function (Meta) { Meta.tags = {}; - Meta.tags.parse = function (meta, link, callback) { + Meta.tags.parse = function (req, meta, link, callback) { async.parallel({ tags: function (next) { var defaultTags = [{ @@ -120,7 +120,23 @@ module.exports = function (Meta) { return tag; }); - addDescription(meta); + addIfNotExists(meta, 'property', 'og:title', Meta.config.title || 'NodeBB'); + + var ogUrl = nconf.get('url') + req.path; + addIfNotExists(meta, 'property', 'og:url', ogUrl); + + addIfNotExists(meta, 'name', 'description', Meta.config.description); + addIfNotExists(meta, 'property', 'og:description', Meta.config.description); + + var ogImage = Meta.config['og:image'] || Meta.config['brand:logo'] || ''; + if (ogImage && !ogImage.startsWith('http')) { + ogImage = nconf.get('url') + ogImage; + } + addIfNotExists(meta, 'property', 'og:image', ogImage); + if (ogImage) { + addIfNotExists(meta, 'property', 'og:image:width', 200); + addIfNotExists(meta, 'property', 'og:image:height', 200); + } link = results.links.concat(link || []); @@ -131,19 +147,20 @@ module.exports = function (Meta) { }); }; - function addDescription(meta) { - var hasDescription = false; + function addIfNotExists(meta, keyName, tagName, value) { + var exists = false; meta.forEach(function (tag) { - if (tag.name === 'description') { - hasDescription = true; + if (tag[keyName] === tagName) { + exists = true; } }); - if (!hasDescription && Meta.config.description) { - meta.push({ - name: 'description', - content: validator.escape(String(Meta.config.description)), - }); + if (!exists && value) { + var data = { + content: validator.escape(String(value)), + }; + data[keyName] = tagName; + meta.push(data); } } }; diff --git a/src/meta/themes.js b/src/meta/themes.js index 8853c9a086..0d3be261e0 100644 --- a/src/meta/themes.js +++ b/src/meta/themes.js @@ -27,18 +27,28 @@ module.exports = function (Meta) { async.filter(files, function (file, next) { fs.stat(path.join(themePath, file), function (err, fileStat) { if (err) { - return next(false); + if (err.code === 'ENOENT') { + return next(null, false); + } + return next(err); } - next((fileStat.isDirectory() && file.slice(0, 13) === 'nodebb-theme-')); + next(null, (fileStat.isDirectory() && file.slice(0, 13) === 'nodebb-theme-')); }); - }, function (themes) { + }, function (err, themes) { + if (err) { + return callback(err); + } + async.map(themes, function (theme, next) { var config = path.join(themePath, theme, 'theme.json'); fs.readFile(config, function (err, file) { if (err) { - return next(); + if (err.code === 'ENOENT') { + return next(null, null); + } + return next(err); } try { var configObj = JSON.parse(file.toString()); diff --git a/src/middleware/header.js b/src/middleware/header.js index 70c0755def..f74b38e3e6 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -41,6 +41,7 @@ module.exports = function (middleware) { middleware.renderHeader = function (req, res, data, callback) { var registrationType = meta.config.registrationType || 'normal'; + res.locals.config = res.locals.config || {}; var templateValues = { title: meta.config.title || '', description: meta.config.description || '', @@ -97,7 +98,7 @@ module.exports = function (middleware) { db.get('uid:' + req.uid + ':confirm:email:sent', next); }, navigation: async.apply(navigation.get), - tags: async.apply(meta.tags.parse, res.locals.metaTags, res.locals.linkTags), + tags: async.apply(meta.tags.parse, req, res.locals.metaTags, res.locals.linkTags), banned: async.apply(user.isBanned, req.uid), banReason: async.apply(user.getBannedReason, req.uid), }, next); @@ -105,7 +106,7 @@ module.exports = function (middleware) { function (results, next) { if (results.banned) { req.logout(); - return res.redirect('/?banned=' + (results.banReason || 'no-reason')); + return res.redirect('/'); } results.user.isAdmin = results.isAdmin; @@ -133,6 +134,7 @@ module.exports = function (middleware) { templateValues.customJS = templateValues.useCustomJS ? meta.config.customJS : ''; templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin; templateValues.defaultLang = meta.config.defaultLang || 'en-GB'; + templateValues.userLang = res.locals.config.userLang; templateValues.privateUserInfo = parseInt(meta.config.privateUserInfo, 10) === 1; templateValues.privateTagListing = parseInt(meta.config.privateTagListing, 10) === 1; diff --git a/src/middleware/index.js b/src/middleware/index.js index faf2ad832d..de0bd6797e 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -73,6 +73,29 @@ middleware.ensureSelfOrGlobalPrivilege = function (req, res, next) { ], next); }; +middleware.ensureSelfOrPrivileged = function (req, res, next) { + /* + The "self" part of this middleware hinges on you having used + middleware.exposeUid prior to invoking this middleware. + */ + if (req.user) { + if (parseInt(req.user.uid, 10) === parseInt(res.locals.uid, 10)) { + return next(); + } + + user.isPrivileged(req.uid, function (err, ok) { + if (err) { + return next(err); + } else if (ok) { + return next(); + } + controllers.helpers.notAllowed(req, res); + }); + } else { + controllers.helpers.notAllowed(req, res); + } +}; + middleware.pageView = function (req, res, next) { analytics.pageView({ ip: req.ip, diff --git a/src/notifications.js b/src/notifications.js index 057a86e674..a158a036fb 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -15,42 +15,44 @@ var batch = require('./batch'); var plugins = require('./plugins'); var utils = require('./utils'); -(function (Notifications) { - Notifications.init = function () { - winston.verbose('[notifications.init] Registering jobs.'); - new cron('*/30 * * * *', Notifications.prune, null, true); - }; - - Notifications.get = function (nid, callback) { - Notifications.getMultiple([nid], function (err, notifications) { - callback(err, Array.isArray(notifications) && notifications.length ? notifications[0] : null); - }); - }; - - Notifications.getMultiple = function (nids, callback) { - var keys = nids.map(function (nid) { - return 'notifications:' + nid; - }); - - db.getObjects(keys, function (err, notifications) { - if (err) { - return callback(err); - } - - notifications = notifications.filter(Boolean); - if (!notifications.length) { - return callback(null, []); - } - +var Notifications = module.exports; + +Notifications.startJobs = function () { + winston.verbose('[notifications.init] Registering jobs.'); + new cron('*/30 * * * *', Notifications.prune, null, true); +}; + +Notifications.get = function (nid, callback) { + Notifications.getMultiple([nid], function (err, notifications) { + callback(err, Array.isArray(notifications) && notifications.length ? notifications[0] : null); + }); +}; + +Notifications.getMultiple = function (nids, callback) { + if (!nids.length) { + return setImmediate(callback, null, []); + } + var keys = nids.map(function (nid) { + return 'notifications:' + nid; + }); + + var notifications; + + async.waterfall([ + function (next) { + db.getObjects(keys, next); + }, + function (_notifications, next) { + notifications = _notifications; var userKeys = notifications.map(function (notification) { - return notification.from; + return notification && notification.from; }); - User.getUsersFields(userKeys, ['username', 'userslug', 'picture'], function (err, usersData) { - if (err) { - return callback(err); - } - notifications.forEach(function (notification, index) { + User.getUsersFields(userKeys, ['username', 'userslug', 'picture'], next); + }, + function (usersData, next) { + notifications.forEach(function (notification, index) { + if (notification) { notification.datetimeISO = utils.toISOString(notification.datetime); if (notification.bodyLong) { @@ -66,449 +68,449 @@ var utils = require('./utils'); } else if (notification.image === 'brand:logo' || !notification.image) { notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'; } - }); - - callback(null, notifications); + } }); - }); - }; - - Notifications.filterExists = function (nids, callback) { - // Removes nids that have been pruned - db.isSortedSetMembers('notifications', nids, function (err, exists) { - if (err) { - return callback(err); - } + next(null, notifications); + }, + ], callback); +}; + +Notifications.filterExists = function (nids, callback) { + async.waterfall([ + function (next) { + db.isSortedSetMembers('notifications', nids, next); + }, + function (exists, next) { nids = nids.filter(function (notifId, idx) { return exists[idx]; }); - callback(null, nids); - }); - }; - - 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; + next(null, nids); + }, + ], callback); +}; - var keys = nids.map(function (nid) { - return 'notifications:' + nid; - }); +Notifications.findRelated = function (mergeIds, set, callback) { + // A related notification is one in a zset that has the same mergeId + var _nids; - db.getObjectsFields(keys, ['mergeId'], next); - }, - ], function (err, sets) { - if (err) { - return callback(err); - } + async.waterfall([ + async.apply(db.getSortedSetRevRange, set, 0, -1), + function (nids, next) { + _nids = nids; - sets = sets.map(function (set) { - return set.mergeId; + var keys = nids.map(function (nid) { + return 'notifications:' + nid; }); - callback(null, _nids.filter(function (nid, idx) { - return mergeIds.indexOf(sets[idx]) !== -1; - })); + db.getObjectsFields(keys, ['mergeId'], next); + }, + ], function (err, sets) { + if (err) { + return callback(err); + } + + sets = sets.map(function (set) { + return set.mergeId; }); - }; - Notifications.create = function (data, callback) { - if (!data.nid) { - return callback(new Error('no-notification-id')); + 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')); + } + data.importance = data.importance || 5; + db.getObject('notifications:' + data.nid, function (err, oldNotification) { + if (err) { + return callback(err); } - data.importance = data.importance || 5; - db.getObject('notifications:' + data.nid, function (err, oldNotification) { - if (err) { - return callback(err); - } - if (oldNotification) { - if (parseInt(oldNotification.pid, 10) === parseInt(data.pid, 10) && parseInt(oldNotification.importance, 10) > parseInt(data.importance, 10)) { - return callback(null, null); - } + if (oldNotification) { + if (parseInt(oldNotification.pid, 10) === parseInt(data.pid, 10) && parseInt(oldNotification.importance, 10) > parseInt(data.importance, 10)) { + return callback(null, null); } + } - var now = Date.now(); - data.datetime = now; - async.parallel([ - function (next) { - db.sortedSetAdd('notifications', now, data.nid, next); - }, - function (next) { - db.setObject('notifications:' + data.nid, data, next); - }, - ], function (err) { - callback(err, data); - }); + var now = Date.now(); + data.datetime = now; + async.parallel([ + function (next) { + db.sortedSetAdd('notifications', now, data.nid, next); + }, + function (next) { + db.setObject('notifications:' + data.nid, data, next); + }, + ], function (err) { + callback(err, data); }); - }; + }); +}; - Notifications.push = function (notification, uids, callback) { - callback = callback || function () {}; +Notifications.push = function (notification, uids, callback) { + callback = callback || function () {}; - if (!notification || !notification.nid) { - return callback(); - } + if (!notification || !notification.nid) { + return callback(); + } - if (!Array.isArray(uids)) { - uids = [uids]; - } + if (!Array.isArray(uids)) { + uids = [uids]; + } + + uids = uids.filter(function (uid, index, array) { + return parseInt(uid, 10) && array.indexOf(uid) === index; + }); + + if (!uids.length) { + return callback(); + } - uids = uids.filter(function (uid, index, array) { - return parseInt(uid, 10) && array.indexOf(uid) === index; + setTimeout(function () { + batch.processArray(uids, function (uids, next) { + pushToUids(uids, notification, next); + }, { interval: 1000 }, function (err) { + if (err) { + winston.error(err.stack); + } }); + }, 1000); + + callback(); +}; + +function pushToUids(uids, notification, callback) { + var oneWeekAgo = Date.now() - 604800000; + var unreadKeys = []; + var readKeys = []; + + async.waterfall([ + function (next) { + plugins.fireHook('filter:notification.push', { notification: notification, uids: uids }, next); + }, + function (data, next) { + if (!data || !data.notification || !data.uids || !data.uids.length) { + return callback(); + } + + uids = data.uids; + notification = data.notification; + + uids.forEach(function (uid) { + unreadKeys.push('uid:' + uid + ':notifications:unread'); + readKeys.push('uid:' + uid + ':notifications:read'); + }); - if (!uids.length) { - return callback(); + db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next); + }, + function (next) { + db.sortedSetsRemove(readKeys, notification.nid, next); + }, + function (next) { + db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next); + }, + function (next) { + db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next); + }, + function (next) { + var websockets = require('./socket.io'); + if (websockets.server) { + uids.forEach(function (uid) { + websockets.in('uid_' + uid).emit('event:new_notification', notification); + }); + } + + plugins.fireHook('action:notification.pushed', { notification: notification, uids: uids }); + next(); + }, + ], callback); +} + +Notifications.pushGroup = function (notification, groupName, callback) { + callback = callback || function () {}; + groups.getMembers(groupName, 0, -1, function (err, members) { + if (err || !Array.isArray(members) || !members.length) { + return callback(err); } - setTimeout(function () { - batch.processArray(uids, function (uids, next) { - pushToUids(uids, notification, next); - }, { interval: 1000 }, function (err) { - if (err) { - winston.error(err.stack); - } - }); - }, 1000); + Notifications.push(notification, members, callback); + }); +}; - callback(); - }; +Notifications.pushGroups = function (notification, groupNames, callback) { + callback = callback || function () {}; + groups.getMembersOfGroups(groupNames, function (err, groupMembers) { + if (err) { + return callback(err); + } - function pushToUids(uids, notification, callback) { - var oneWeekAgo = Date.now() - 604800000; - var unreadKeys = []; - var readKeys = []; + var members = _.unique(_.flatten(groupMembers)); + Notifications.push(notification, members, callback); + }); +}; + +Notifications.rescind = function (nid, callback) { + callback = callback || function () {}; + + async.parallel([ + async.apply(db.sortedSetRemove, 'notifications', nid), + async.apply(db.delete, 'notifications:' + nid), + ], function (err) { + if (err) { + winston.error('Encountered error rescinding notification (' + nid + '): ' + err.message); + } else { + winston.verbose('[notifications/rescind] Rescinded notification "' + nid + '"'); + } - async.waterfall([ - function (next) { - plugins.fireHook('filter:notification.push', { notification: notification, uids: uids }, next); - }, - function (data, next) { - if (!data || !data.notification || !data.uids || !data.uids.length) { - return callback(); - } + callback(err, nid); + }); +}; - uids = data.uids; - notification = data.notification; +Notifications.markRead = function (nid, uid, callback) { + callback = callback || function () {}; + if (!parseInt(uid, 10) || !nid) { + return callback(); + } + Notifications.markReadMultiple([nid], uid, callback); +}; - uids.forEach(function (uid) { - unreadKeys.push('uid:' + uid + ':notifications:unread'); - readKeys.push('uid:' + uid + ':notifications:read'); - }); +Notifications.markUnread = function (nid, uid, callback) { + callback = callback || function () {}; + if (!parseInt(uid, 10) || !nid) { + return callback(); + } - db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next); - }, - function (next) { - db.sortedSetsRemove(readKeys, notification.nid, next); - }, - function (next) { - db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next); - }, - function (next) { - db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next); - }, - function (next) { - var websockets = require('./socket.io'); - if (websockets.server) { - uids.forEach(function (uid) { - websockets.in('uid_' + uid).emit('event:new_notification', notification); - }); - } + db.getObject('notifications:' + nid, function (err, notification) { + if (err || !notification) { + return callback(err || new Error('[[error:no-notification]]')); + } + notification.datetime = notification.datetime || Date.now(); - plugins.fireHook('action:notification.pushed', { notification: notification, uids: uids }); - next(); - }, + async.parallel([ + async.apply(db.sortedSetRemove, 'uid:' + uid + ':notifications:read', nid), + async.apply(db.sortedSetAdd, 'uid:' + uid + ':notifications:unread', notification.datetime, nid), ], callback); + }); +}; + +Notifications.markReadMultiple = function (nids, uid, callback) { + callback = callback || function () {}; + nids = nids.filter(Boolean); + if (!Array.isArray(nids) || !nids.length) { + return callback(); } - Notifications.pushGroup = function (notification, groupName, callback) { - callback = callback || function () {}; - groups.getMembers(groupName, 0, -1, function (err, members) { - if (err || !Array.isArray(members) || !members.length) { - return callback(err); - } + var notificationKeys = nids.map(function (nid) { + return 'notifications:' + nid; + }); - Notifications.push(notification, members, callback); - }); - }; + 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.pushGroups = function (notification, groupNames, callback) { - callback = callback || function () {}; - groups.getMembersOfGroups(groupNames, function (err, groupMembers) { - if (err) { - return callback(err); - } + Notifications.findRelated(mergeIds, 'uid:' + uid + ':notifications:unread', next); + }, + function (relatedNids, next) { + notificationKeys = _.union(nids, relatedNids).map(function (nid) { + return 'notifications:' + nid; + }); - var members = _.unique(_.flatten(groupMembers)); + db.getObjectsFields(notificationKeys, ['nid', 'datetime'], next); + }, + ], function (err, notificationData) { + if (err) { + return callback(err); + } - Notifications.push(notification, members, callback); + // Filter out notifications that didn't exist + notificationData = notificationData.filter(function (notification) { + return notification && notification.nid; }); - }; - Notifications.rescind = function (nid, callback) { - callback = callback || function () {}; + // Extract nid + nids = notificationData.map(function (notification) { + return notification.nid; + }); + + var datetimes = notificationData.map(function (notification) { + return (notification && notification.datetime) || Date.now(); + }); async.parallel([ - async.apply(db.sortedSetRemove, 'notifications', nid), - async.apply(db.delete, 'notifications:' + nid), + function (next) { + db.sortedSetRemove('uid:' + uid + ':notifications:unread', nids, next); + }, + function (next) { + db.sortedSetAdd('uid:' + uid + ':notifications:read', datetimes, nids, next); + }, ], function (err) { - if (err) { - winston.error('Encountered error rescinding notification (' + nid + '): ' + err.message); - } else { - winston.verbose('[notifications/rescind] Rescinded notification "' + nid + '"'); - } - - callback(err, nid); + callback(err); }); - }; + }); +}; - Notifications.markRead = function (nid, uid, callback) { - callback = callback || function () {}; - if (!parseInt(uid, 10) || !nid) { - return callback(); +Notifications.markAllRead = function (uid, callback) { + db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, function (err, nids) { + if (err) { + return callback(err); } - Notifications.markReadMultiple([nid], uid, callback); - }; - Notifications.markUnread = function (nid, uid, callback) { - callback = callback || function () {}; - if (!parseInt(uid, 10) || !nid) { + if (!Array.isArray(nids) || !nids.length) { return callback(); } - db.getObject('notifications:' + nid, function (err, notification) { - if (err || !notification) { - return callback(err || new Error('[[error:no-notification]]')); - } - notification.datetime = notification.datetime || Date.now(); + Notifications.markReadMultiple(nids, uid, callback); + }); +}; - async.parallel([ - async.apply(db.sortedSetRemove, 'uid:' + uid + ':notifications:read', nid), - async.apply(db.sortedSetAdd, 'uid:' + uid + ':notifications:unread', notification.datetime, nid), - ], callback); - }); - }; +Notifications.prune = function () { + var week = 604800000; + + var cutoffTime = Date.now() - week; + + db.getSortedSetRangeByScore('notifications', 0, 500, '-inf', cutoffTime, function (err, nids) { + if (err) { + return winston.error(err.message); + } - Notifications.markReadMultiple = function (nids, uid, callback) { - callback = callback || function () {}; - nids = nids.filter(Boolean); if (!Array.isArray(nids) || !nids.length) { - return callback(); + return; } - var notificationKeys = nids.map(function (nid) { + var keys = nids.map(function (nid) { return 'notifications:' + nid; }); - 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); + async.parallel([ + function (next) { + db.sortedSetRemove('notifications', nids, next); }, - function (relatedNids, next) { - notificationKeys = _.union(nids, relatedNids).map(function (nid) { - return 'notifications:' + nid; - }); - - db.getObjectsFields(notificationKeys, ['nid', 'datetime'], next); + function (next) { + db.deleteAll(keys, next); }, - ], function (err, notificationData) { + ], function (err) { if (err) { - return callback(err); + return winston.error('Encountered error pruning notifications: ' + err.message); } - - // 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; - }); - - var datetimes = notificationData.map(function (notification) { - return (notification && notification.datetime) || Date.now(); - }); - - async.parallel([ - function (next) { - db.sortedSetRemove('uid:' + uid + ':notifications:unread', nids, next); - }, - function (next) { - db.sortedSetAdd('uid:' + uid + ':notifications:read', datetimes, nids, next); - }, - ], function (err) { - callback(err); - }); }); - }; - - Notifications.markAllRead = function (uid, callback) { - db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, function (err, nids) { - if (err) { - return callback(err); - } - - if (!Array.isArray(nids) || !nids.length) { - return callback(); + }); +}; + +Notifications.merge = function (notifications, callback) { + // When passed a set of notification objects, merge any that can be merged + var mergeIds = [ + 'notifications:upvoted_your_post_in', + 'notifications:user_started_following_you', + 'notifications:user_posted_to', + 'notifications:user_flagged_post_in', + 'notifications:user_flagged_user', + 'new_register', + ]; + var isolated; + var differentiators; + var differentiator; + var modifyIndex; + var set; + + notifications = mergeIds.reduce(function (notifications, mergeId) { + isolated = notifications.filter(function (notifObj) { + if (!notifObj || !notifObj.hasOwnProperty('mergeId')) { + return false; } - Notifications.markReadMultiple(nids, uid, callback); + return notifObj.mergeId.split('|')[0] === mergeId; }); - }; - - Notifications.prune = function () { - var week = 604800000; - - var cutoffTime = Date.now() - week; - db.getSortedSetRangeByScore('notifications', 0, 500, '-inf', cutoffTime, function (err, nids) { - if (err) { - return winston.error(err.message); - } + if (isolated.length <= 1) { + return notifications; // Nothing to merge + } - if (!Array.isArray(nids) || !nids.length) { - return; + // Each isolated mergeId may have multiple differentiators, so process each separately + differentiators = isolated.reduce(function (cur, next) { + differentiator = next.mergeId.split('|')[1] || 0; + if (cur.indexOf(differentiator) === -1) { + cur.push(differentiator); } - var keys = nids.map(function (nid) { - return 'notifications:' + nid; - }); - - async.parallel([ - function (next) { - db.sortedSetRemove('notifications', nids, next); - }, - function (next) { - db.deleteAll(keys, next); - }, - ], function (err) { - if (err) { - return winston.error('Encountered error pruning notifications: ' + err.message); - } - }); - }); - }; - - Notifications.merge = function (notifications, callback) { - // When passed a set of notification objects, merge any that can be merged - var mergeIds = [ - 'notifications:upvoted_your_post_in', - 'notifications:user_started_following_you', - 'notifications:user_posted_to', - 'notifications:user_flagged_post_in', - 'new_register', - ]; - var isolated; - var differentiators; - var differentiator; - var modifyIndex; - var set; - - notifications = mergeIds.reduce(function (notifications, mergeId) { - isolated = notifications.filter(function (notifObj) { - if (!notifObj || !notifObj.hasOwnProperty('mergeId')) { - return false; - } - - return notifObj.mergeId.split('|')[0] === mergeId; - }); + return cur; + }, []); - if (isolated.length <= 1) { - return notifications; // Nothing to merge + differentiators.forEach(function (differentiator) { + if (differentiator === 0 && differentiators.length === 1) { + set = isolated; + } else { + set = isolated.filter(function (notifObj) { + return notifObj.mergeId === (mergeId + '|' + differentiator); + }); } - // Each isolated mergeId may have multiple differentiators, so process each separately - differentiators = isolated.reduce(function (cur, next) { - differentiator = next.mergeId.split('|')[1] || 0; - if (cur.indexOf(differentiator) === -1) { - cur.push(differentiator); - } + modifyIndex = notifications.indexOf(set[0]); + if (modifyIndex === -1 || set.length === 1) { + return notifications; + } - return cur; - }, []); + switch (mergeId) { + // intentional fall-through + case 'notifications:upvoted_your_post_in': + case 'notifications:user_started_following_you': + case 'notifications:user_posted_to': + case 'notifications:user_flagged_post_in': + case 'notifications:user_flagged_user': + var usernames = set.map(function (notifObj) { + return notifObj && notifObj.user && notifObj.user.username; + }).filter(function (username, idx, array) { + return array.indexOf(username) === idx; + }); + var numUsers = usernames.length; - differentiators.forEach(function (differentiator) { - if (differentiator === 0 && differentiators.length === 1) { - set = isolated; - } else { - set = isolated.filter(function (notifObj) { - return notifObj.mergeId === (mergeId + '|' + differentiator); - }); - } + var title = S(notifications[modifyIndex].topicTitle || '').decodeHTMLEntities().s; + var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); + titleEscaped = titleEscaped ? (', ' + titleEscaped) : ''; - modifyIndex = notifications.indexOf(set[0]); - if (modifyIndex === -1 || set.length === 1) { - return notifications; + if (numUsers === 2) { + notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + titleEscaped + ']]'; + } else if (numUsers > 2) { + notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers - 1) + titleEscaped + ']]'; } - switch (mergeId) { - // intentional fall-through - case 'notifications:upvoted_your_post_in': - case 'notifications:user_started_following_you': - case 'notifications:user_posted_to': - case 'notifications:user_flagged_post_in': - var usernames = set.map(function (notifObj) { - return notifObj && notifObj.user && notifObj.user.username; - }).filter(function (username, idx, array) { - return array.indexOf(username) === idx; - }); - var numUsers = usernames.length; - - var title = S(notifications[modifyIndex].topicTitle || '').decodeHTMLEntities().s; - var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); - titleEscaped = titleEscaped ? (', ' + titleEscaped) : ''; - - if (numUsers === 2) { - notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + titleEscaped + ']]'; - } else if (numUsers > 2) { - notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers - 1) + titleEscaped + ']]'; - } + notifications[modifyIndex].path = set[set.length - 1].path; + break; - notifications[modifyIndex].path = set[set.length - 1].path; - break; + case 'new_register': + notifications[modifyIndex].bodyShort = '[[notifications:' + mergeId + '_multiple, ' + set.length + ']]'; + break; + } - case 'new_register': - notifications[modifyIndex].bodyShort = '[[notifications:' + mergeId + '_multiple, ' + set.length + ']]'; - break; + // Filter out duplicates + notifications = notifications.filter(function (notifObj, idx) { + if (!notifObj || !notifObj.mergeId) { + return true; } - // Filter out duplicates - notifications = notifications.filter(function (notifObj, idx) { - if (!notifObj || !notifObj.mergeId) { - return true; - } - - return !(notifObj.mergeId === (mergeId + (differentiator ? '|' + differentiator : '')) && idx !== modifyIndex); - }); + return !(notifObj.mergeId === (mergeId + (differentiator ? '|' + differentiator : '')) && idx !== modifyIndex); }); - - return notifications; - }, notifications); - - plugins.fireHook('filter:notifications.merge', { - notifications: notifications, - }, function (err, data) { - callback(err, data.notifications); }); - }; -}(exports)); + return notifications; + }, notifications); + + plugins.fireHook('filter:notifications.merge', { + notifications: notifications, + }, function (err, data) { + callback(err, data.notifications); + }); +}; diff --git a/src/pagination.js b/src/pagination.js index 3e57c5b201..4058e7f8f4 100644 --- a/src/pagination.js +++ b/src/pagination.js @@ -1,6 +1,7 @@ 'use strict'; var qs = require('querystring'); +var _ = require('underscore'); var pagination = {}; @@ -37,7 +38,7 @@ pagination.create = function (currentPage, pageCount, queryObj) { return a - b; }); - queryObj = queryObj || {}; + queryObj = _.clone(queryObj || {}); delete queryObj._; diff --git a/src/password.js b/src/password.js index 816e357d12..5405941fff 100644 --- a/src/password.js +++ b/src/password.js @@ -9,6 +9,9 @@ }; module.compare = function (password, hash, callback) { + if (!hash || !password) { + return setImmediate(callback, null, false); + } forkChild({ type: 'compare', password: password, hash: hash }, callback); }; diff --git a/src/plugins.js b/src/plugins.js index e4f1fa8312..6c69553a05 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -159,9 +159,7 @@ var middleware; }); // Filter out plugins with invalid paths - async.filter(paths, file.exists, function (paths) { - next(null, paths); - }); + async.filter(paths, file.exists, next); }, function (paths, next) { async.map(paths, Plugins.loadPluginInfo, next); @@ -341,11 +339,15 @@ var middleware; async.filter(dirs, function (dir, callback) { fs.stat(dir, function (err, stats) { - callback(!err && stats.isDirectory()); + if (err) { + if (err.code === 'ENOENT') { + return callback(null, false); + } + return callback(err); + } + callback(null, stats.isDirectory()); }); - }, function (plugins) { - next(null, plugins); - }); + }, next); }, function (files, next) { diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 12303555fc..16ceb32878 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -8,6 +8,7 @@ module.exports = function (Plugins) { 'filter:user.custom_fields': null, // remove in v1.1.0 'filter:post.save': 'filter:post.create', 'filter:user.profileLinks': 'filter:user.profileMenu', + 'action:post.flag': 'action:flag.create', }; /* `data` is an object consisting of (* is required): diff --git a/src/plugins/install.js b/src/plugins/install.js index 1e0fe0838c..a5ba2db1dd 100644 --- a/src/plugins/install.js +++ b/src/plugins/install.js @@ -49,8 +49,8 @@ module.exports = function (Plugins) { }, function (next) { meta.reloadRequired = true; - Plugins.fireHook(isActive ? 'action:plugin.deactivate' : 'action:plugin.activate', id); - next(); + Plugins.fireHook(isActive ? 'action:plugin.deactivate' : 'action:plugin.activate', { id: id }); + setImmediate(next); }, ], function (err) { if (err) { @@ -67,8 +67,8 @@ module.exports = function (Plugins) { }; function toggleInstall(id, version, callback) { - var type; var installed; + var type; async.waterfall([ function (next) { Plugins.isInstalled(id, next); @@ -85,7 +85,7 @@ module.exports = function (Plugins) { }); return; } - next(); + setImmediate(next); }, function (next) { runNpmCommand(type, id, version || 'latest', next); @@ -94,8 +94,8 @@ module.exports = function (Plugins) { Plugins.get(id, next); }, function (pluginData, next) { - Plugins.fireHook('action:plugin.' + type, id); - next(null, pluginData); + Plugins.fireHook('action:plugin.' + type, { id: id, version: version }); + setImmediate(next, null, pluginData); }, ], callback); } diff --git a/src/plugins/load.js b/src/plugins/load.js index 3c1c61df73..f4ba868eea 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -29,9 +29,7 @@ module.exports = function (Plugins) { return path.join(__dirname, '../../node_modules/', plugin); }); - async.filter(plugins, file.exists, function (plugins) { - next(null, plugins); - }); + async.filter(plugins, file.exists, next); }, ], callback); }; @@ -166,13 +164,13 @@ module.exports = function (Plugins) { var realPath = pluginData.staticDirs[mappedPath]; var staticDir = path.join(pluginPath, realPath); - file.exists(staticDir, function (exists) { + file.exists(staticDir, function (err, exists) { if (exists) { Plugins.staticDirs[pluginData.id + '/' + mappedPath] = staticDir; } else { winston.warn('[plugins/' + pluginData.id + '] Mapped path \'' + mappedPath + ' => ' + staticDir + '\' not found.'); } - callback(); + callback(err); }); } } diff --git a/src/posts.js b/src/posts.js index 4b9febae07..492599e4cd 100644 --- a/src/posts.js +++ b/src/posts.js @@ -10,259 +10,277 @@ var topics = require('./topics'); var privileges = require('./privileges'); var plugins = require('./plugins'); -(function (Posts) { - require('./posts/create')(Posts); - require('./posts/delete')(Posts); - require('./posts/edit')(Posts); - require('./posts/parse')(Posts); - require('./posts/user')(Posts); - require('./posts/topics')(Posts); - require('./posts/category')(Posts); - require('./posts/summary')(Posts); - require('./posts/recent')(Posts); - require('./posts/flags')(Posts); - require('./posts/tools')(Posts); - require('./posts/votes')(Posts); - require('./posts/bookmarks')(Posts); +var Posts = module.exports; - Posts.exists = function (pid, callback) { - db.isSortedSetMember('posts:pid', pid, callback); - }; +require('./posts/create')(Posts); +require('./posts/delete')(Posts); +require('./posts/edit')(Posts); +require('./posts/parse')(Posts); +require('./posts/user')(Posts); +require('./posts/topics')(Posts); +require('./posts/category')(Posts); +require('./posts/summary')(Posts); +require('./posts/recent')(Posts); +require('./posts/tools')(Posts); +require('./posts/votes')(Posts); +require('./posts/bookmarks')(Posts); - Posts.getPidsFromSet = function (set, start, stop, reverse, callback) { - if (isNaN(start) || isNaN(stop)) { - return callback(null, []); - } - db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop, callback); - }; - - Posts.getPostsByPids = function (pids, uid, callback) { - if (!Array.isArray(pids) || !pids.length) { - return callback(null, []); - } +Posts.exists = function (pid, callback) { + db.isSortedSetMember('posts:pid', pid, callback); +}; - var keys = []; +Posts.getPidsFromSet = function (set, start, stop, reverse, callback) { + if (isNaN(start) || isNaN(stop)) { + return callback(null, []); + } + db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop, callback); +}; - for (var x = 0, numPids = pids.length; x < numPids; x += 1) { - keys.push('post:' + pids[x]); - } +Posts.getPostsByPids = function (pids, uid, callback) { + if (!Array.isArray(pids) || !pids.length) { + return callback(null, []); + } - async.waterfall([ - function (next) { - db.getObjects(keys, next); - }, - function (posts, next) { - async.map(posts, function (post, next) { - if (!post) { - return next(); - } - post.upvotes = parseInt(post.upvotes, 10) || 0; - post.downvotes = parseInt(post.downvotes, 10) || 0; - post.votes = post.upvotes - post.downvotes; - post.timestampISO = utils.toISOString(post.timestamp); - post.editedISO = parseInt(post.edited, 10) !== 0 ? utils.toISOString(post.edited) : ''; - Posts.parsePost(post, next); - }, next); - }, - function (posts, next) { - plugins.fireHook('filter:post.getPosts', { posts: posts, uid: uid }, next); - }, - function (data, next) { - if (!data || !Array.isArray(data.posts)) { - return next(null, []); + async.waterfall([ + function (next) { + var keys = pids.map(function (pid) { + return 'post:' + pid; + }); + db.getObjects(keys, next); + }, + function (posts, next) { + async.map(posts, function (post, next) { + if (!post) { + return next(); } - data.posts = data.posts.filter(Boolean); - next(null, data.posts); - }, - ], callback); - }; - - Posts.getPostSummariesFromSet = function (set, uid, start, stop, callback) { - async.waterfall([ - function (next) { - db.getSortedSetRevRange(set, start, stop, next); - }, - function (pids, next) { - privileges.posts.filter('read', pids, uid, next); - }, - function (pids, next) { - Posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next); - }, - function (posts, next) { - next(null, { posts: posts, nextStart: stop + 1 }); - }, - ], callback); - }; - - Posts.getPostData = function (pid, callback) { - db.getObject('post:' + pid, function (err, data) { - if (err) { - return callback(err); + post.upvotes = parseInt(post.upvotes, 10) || 0; + post.downvotes = parseInt(post.downvotes, 10) || 0; + post.votes = post.upvotes - post.downvotes; + post.timestampISO = utils.toISOString(post.timestamp); + post.editedISO = parseInt(post.edited, 10) !== 0 ? utils.toISOString(post.edited) : ''; + Posts.parsePost(post, next); + }, next); + }, + function (posts, next) { + plugins.fireHook('filter:post.getPosts', { posts: posts, uid: uid }, next); + }, + function (data, next) { + if (!data || !Array.isArray(data.posts)) { + return next(null, []); } + data.posts = data.posts.filter(Boolean); + next(null, data.posts); + }, + ], callback); +}; - plugins.fireHook('filter:post.get', data, callback); - }); - }; - - Posts.getPostField = function (pid, field, callback) { - Posts.getPostFields(pid, [field], function (err, data) { - if (err) { - return callback(err); - } +Posts.getPostSummariesFromSet = function (set, uid, start, stop, callback) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange(set, start, stop, next); + }, + function (pids, next) { + privileges.posts.filter('read', pids, uid, next); + }, + function (pids, next) { + Posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next); + }, + function (posts, next) { + next(null, { posts: posts, nextStart: stop + 1 }); + }, + ], callback); +}; - callback(null, data[field]); - }); - }; +Posts.getPostData = function (pid, callback) { + async.waterfall([ + function (next) { + db.getObject('post:' + pid, next); + }, + function (data, next) { + plugins.fireHook('filter:post.getPostData', { post: data }, next); + }, + function (data, next) { + next(null, data.post); + }, + ], callback); +}; - Posts.getPostFields = function (pid, fields, callback) { - db.getObjectFields('post:' + pid, fields, function (err, data) { - if (err) { - return callback(err); - } +Posts.getPostField = function (pid, field, callback) { + async.waterfall([ + function (next) { + Posts.getPostFields(pid, [field], next); + }, + function (data, next) { + next(null, data[field]); + }, + ], callback); +}; +Posts.getPostFields = function (pid, fields, callback) { + async.waterfall([ + function (next) { + db.getObjectFields('post:' + pid, fields, next); + }, + function (data, next) { data.pid = pid; - plugins.fireHook('filter:post.getFields', { posts: [data], fields: fields }, function (err, data) { - callback(err, (data && Array.isArray(data.posts) && data.posts.length) ? data.posts[0] : null); - }); - }); - }; + plugins.fireHook('filter:post.getFields', { posts: [data], fields: fields }, next); + }, + function (data, next) { + next(null, (data && Array.isArray(data.posts) && data.posts.length) ? data.posts[0] : null); + }, + ], callback); +}; - Posts.getPostsFields = function (pids, fields, callback) { - if (!Array.isArray(pids) || !pids.length) { - return callback(null, []); - } +Posts.getPostsFields = function (pids, fields, callback) { + if (!Array.isArray(pids) || !pids.length) { + return callback(null, []); + } - var keys = pids.map(function (pid) { - return 'post:' + pid; - }); + var keys = pids.map(function (pid) { + return 'post:' + pid; + }); - db.getObjectsFields(keys, fields, function (err, posts) { - if (err) { - return callback(err); - } - plugins.fireHook('filter:post.getFields', { posts: posts, fields: fields }, function (err, data) { - callback(err, (data && Array.isArray(data.posts)) ? data.posts : null); - }); - }); - }; + async.waterfall([ + function (next) { + db.getObjectsFields(keys, fields, next); + }, + function (posts, next) { + plugins.fireHook('filter:post.getFields', { posts: posts, fields: fields }, next); + }, + function (data, next) { + next(null, (data && Array.isArray(data.posts)) ? data.posts : null); + }, + ], callback); +}; - Posts.setPostField = function (pid, field, value, callback) { - db.setObjectField('post:' + pid, field, value, function (err) { - if (err) { - return callback(err); - } +Posts.setPostField = function (pid, field, value, callback) { + async.waterfall([ + function (next) { + db.setObjectField('post:' + pid, field, value, next); + }, + function (next) { var data = { pid: pid, }; data[field] = value; - plugins.fireHook('action:post.setFields', data); - callback(); - }); - }; + plugins.fireHook('action:post.setFields', { data: data }); + next(); + }, + ], callback); +}; - Posts.setPostFields = function (pid, data, callback) { - db.setObject('post:' + pid, data, function (err) { - if (err) { - return callback(err); - } +Posts.setPostFields = function (pid, data, callback) { + async.waterfall([ + function (next) { + db.setObject('post:' + pid, data, next); + }, + function (next) { data.pid = pid; - plugins.fireHook('action:post.setFields', data); - callback(); - }); - }; + plugins.fireHook('action:post.setFields', { data: data }); + next(); + }, + ], callback); +}; - Posts.getPidIndex = function (pid, tid, topicPostSort, callback) { - var set = topicPostSort === 'most_votes' ? 'tid:' + tid + ':posts:votes' : 'tid:' + tid + ':posts'; - db.sortedSetRank(set, pid, function (err, index) { +Posts.getPidIndex = function (pid, tid, topicPostSort, callback) { + async.waterfall([ + function (next) { + var set = topicPostSort === 'most_votes' ? 'tid:' + tid + ':posts:votes' : 'tid:' + tid + ':posts'; + db.sortedSetRank(set, pid, next); + }, + function (index, next) { if (!utils.isNumber(index)) { - return callback(err, 0); + return next(null, 0); } - callback(err, parseInt(index, 10) + 1); - }); - }; + next(null, parseInt(index, 10) + 1); + }, + ], callback); +}; - Posts.getPostIndices = function (posts, uid, callback) { - if (!Array.isArray(posts) || !posts.length) { - return callback(null, []); - } +Posts.getPostIndices = function (posts, uid, callback) { + if (!Array.isArray(posts) || !posts.length) { + return callback(null, []); + } - async.waterfall([ - function (next) { - user.getSettings(uid, next); - }, - function (settings, next) { - var byVotes = settings.topicPostSort === 'most_votes'; - var sets = posts.map(function (post) { - return byVotes ? 'tid:' + post.tid + ':posts:votes' : 'tid:' + post.tid + ':posts'; - }); + async.waterfall([ + function (next) { + user.getSettings(uid, next); + }, + function (settings, next) { + var byVotes = settings.topicPostSort === 'most_votes'; + var sets = posts.map(function (post) { + return byVotes ? 'tid:' + post.tid + ':posts:votes' : 'tid:' + post.tid + ':posts'; + }); - var uniqueSets = _.uniq(sets); - var method = 'sortedSetsRanks'; - if (uniqueSets.length === 1) { - method = 'sortedSetRanks'; - sets = uniqueSets[0]; - } + var uniqueSets = _.uniq(sets); + var method = 'sortedSetsRanks'; + if (uniqueSets.length === 1) { + method = 'sortedSetRanks'; + sets = uniqueSets[0]; + } - var pids = posts.map(function (post) { - return post.pid; - }); + var pids = posts.map(function (post) { + return post.pid; + }); - db[method](sets, pids, next); - }, - function (indices, next) { - for (var i = 0; i < indices.length; i += 1) { - indices[i] = utils.isNumber(indices[i]) ? parseInt(indices[i], 10) + 1 : 0; - } + db[method](sets, pids, next); + }, + function (indices, next) { + for (var i = 0; i < indices.length; i += 1) { + indices[i] = utils.isNumber(indices[i]) ? parseInt(indices[i], 10) + 1 : 0; + } - next(null, indices); - }, - ], callback); - }; + next(null, indices); + }, + ], callback); +}; - Posts.updatePostVoteCount = function (postData, callback) { - if (!postData || !postData.pid || !postData.tid) { - return callback(); - } - async.parallel([ - function (next) { - if (postData.uid) { - if (postData.votes > 0) { - db.sortedSetAdd('uid:' + postData.uid + ':posts:votes', postData.votes, postData.pid, next); - } else { - db.sortedSetRemove('uid:' + postData.uid + ':posts:votes', postData.pid, next); - } +Posts.updatePostVoteCount = function (postData, callback) { + if (!postData || !postData.pid || !postData.tid) { + return callback(); + } + async.parallel([ + function (next) { + if (postData.uid) { + if (postData.votes > 0) { + db.sortedSetAdd('uid:' + postData.uid + ':posts:votes', postData.votes, postData.pid, next); } else { - next(); + db.sortedSetRemove('uid:' + postData.uid + ':posts:votes', postData.pid, next); } - }, - function (next) { - async.waterfall([ - function (next) { - topics.getTopicField(postData.tid, 'mainPid', next); - }, - function (mainPid, next) { - if (parseInt(mainPid, 10) === parseInt(postData.pid, 10)) { - return next(); - } - db.sortedSetAdd('tid:' + postData.tid + ':posts:votes', postData.votes, postData.pid, next); - }, - ], next); - }, - function (next) { - Posts.setPostFields(postData.pid, { upvotes: postData.upvotes, downvotes: postData.downvotes }, next); - }, - ], function (err) { - callback(err); - }); - }; - - Posts.modifyPostByPrivilege = function (post, isAdminOrMod) { - if (post.deleted && !(isAdminOrMod || post.selfPost)) { - post.content = '[[topic:post_is_deleted]]'; - if (post.user) { - post.user.signature = ''; + } else { + next(); } + }, + function (next) { + async.waterfall([ + function (next) { + topics.getTopicField(postData.tid, 'mainPid', next); + }, + function (mainPid, next) { + if (parseInt(mainPid, 10) === parseInt(postData.pid, 10)) { + return next(); + } + db.sortedSetAdd('tid:' + postData.tid + ':posts:votes', postData.votes, postData.pid, next); + }, + ], next); + }, + function (next) { + db.sortedSetAdd('posts:votes', postData.votes, postData.pid, next); + }, + function (next) { + Posts.setPostFields(postData.pid, { upvotes: postData.upvotes, downvotes: postData.downvotes }, next); + }, + ], function (err) { + callback(err); + }); +}; + +Posts.modifyPostByPrivilege = function (post, isAdminOrMod) { + if (post.deleted && !(isAdminOrMod || post.selfPost)) { + post.content = '[[topic:post_is_deleted]]'; + if (post.user) { + post.user.signature = ''; } - }; -}(exports)); + } +}; diff --git a/src/posts/create.js b/src/posts/create.js index 86faf0ffa9..75b0f8321e 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -9,6 +9,7 @@ var plugins = require('../plugins'); var user = require('../user'); var topics = require('../topics'); var categories = require('../categories'); +var groups = require('../groups'); var utils = require('../utils'); module.exports = function (Posts) { @@ -82,6 +83,9 @@ module.exports = function (Posts) { categories.onNewPostMade(topicData.cid, topicData.pinned, postData, next); }); }, + function (next) { + groups.onNewPostMade(postData, next); + }, function (next) { db.sortedSetAdd('posts:pid', timestamp, postData.pid, next); }, @@ -101,13 +105,13 @@ module.exports = function (Posts) { if (err) { return next(err); } - plugins.fireHook('filter:post.get', postData, next); + plugins.fireHook('filter:post.get', { post: postData, uid: data.uid }, next); }); }, - function (postData, next) { - postData.isMain = isMain; - plugins.fireHook('action:post.save', _.clone(postData)); - next(null, postData); + function (data, next) { + data.post.isMain = isMain; + plugins.fireHook('action:post.save', { post: _.clone(data.post) }); + next(null, data.post); }, ], callback); }; diff --git a/src/posts/delete.js b/src/posts/delete.js index 63f511481b..61793f68df 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -6,6 +6,7 @@ var _ = require('underscore'); var db = require('../database'); var topics = require('../topics'); var user = require('../user'); +var groups = require('../groups'); var notifications = require('../notifications'); var plugins = require('../plugins'); @@ -27,6 +28,7 @@ module.exports = function (Posts) { topics.getTopicFields(_post.tid, ['tid', 'cid', 'pinned'], next); }, function (topicData, next) { + postData.cid = topicData.cid; async.parallel([ function (next) { updateTopicTimestamp(topicData, next); @@ -40,7 +42,7 @@ module.exports = function (Posts) { ], next); }, function (results, next) { - plugins.fireHook('action:post.delete', pid); + plugins.fireHook('action:post.delete', { post: _.clone(postData), uid: uid }); next(null, postData); }, ], callback); @@ -77,7 +79,7 @@ module.exports = function (Posts) { ], next); }, function (results, next) { - plugins.fireHook('action:post.restore', _.clone(postData)); + plugins.fireHook('action:post.restore', { post: _.clone(postData), uid: uid }); next(null, postData); }, ], callback); @@ -141,19 +143,22 @@ module.exports = function (Posts) { deletePostFromReplies(pid, next); }, function (next) { - db.sortedSetsRemove(['posts:pid', 'posts:flagged'], pid, next); + deletePostFromGroups(pid, next); }, function (next) { - Posts.dismissFlag(pid, next); + db.sortedSetsRemove(['posts:pid', 'posts:flagged'], pid, next); }, ], function (err) { - if (err) { - return next(err); - } - plugins.fireHook('action:post.purge', pid); - db.delete('post:' + pid, next); + next(err); }); }, + function (next) { + Posts.getPostData(pid, next); + }, + function (postData, next) { + plugins.fireHook('action:post.purge', { post: postData, uid: uid }); + db.delete('post:' + pid, next); + }, ], callback); }; @@ -293,4 +298,26 @@ module.exports = function (Posts) { ], callback); }); } + + function deletePostFromGroups(pid, callback) { + async.waterfall([ + function (next) { + Posts.getPostField(pid, 'uid', next); + }, + function (uid, next) { + if (!parseInt(uid, 10)) { + return callback(); + } + groups.getUserGroupMembership('groups:visible:createtime', [uid], next); + }, + function (groupNames, next) { + groupNames = groupNames[0]; + var keys = groupNames.map(function (groupName) { + return 'group:' + groupName + ':member:pids'; + }); + + db.sortedSetsRemove(keys, pid, next); + }, + ], callback); + } }; diff --git a/src/posts/edit.js b/src/posts/edit.js index 3e9d295f19..99cc8e1692 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -65,7 +65,7 @@ module.exports = function (Posts) { postData.cid = results.topic.cid; postData.topic = results.topic; - plugins.fireHook('action:post.edit', _.clone(postData)); + plugins.fireHook('action:post.edit', { post: _.clone(postData), uid: data.uid }); cache.del(String(postData.pid)); pubsub.publish('post:edit', String(postData.pid)); @@ -85,7 +85,7 @@ module.exports = function (Posts) { async.parallel({ topic: function (next) { - topics.getTopicFields(tid, ['cid', 'title'], next); + topics.getTopicFields(tid, ['cid', 'title', 'timestamp'], next); }, isMain: function (next) { Posts.isMain(data.pid, next); @@ -136,7 +136,8 @@ module.exports = function (Posts) { function (tags, next) { topicData.tags = data.tags; topicData.oldTitle = results.topic.title; - plugins.fireHook('action:topic.edit', topicData); + topicData.timestamp = results.topic.timestamp; + plugins.fireHook('action:topic.edit', { topic: topicData, uid: data.uid }); next(null, { tid: tid, cid: results.topic.cid, diff --git a/src/posts/flags.js b/src/posts/flags.js deleted file mode 100644 index 214a155278..0000000000 --- a/src/posts/flags.js +++ /dev/null @@ -1,418 +0,0 @@ - - -'use strict'; - -var async = require('async'); -var winston = require('winston'); -var db = require('../database'); -var user = require('../user'); -var analytics = require('../analytics'); - -module.exports = function (Posts) { - Posts.flag = function (post, uid, reason, callback) { - if (!parseInt(uid, 10) || !reason) { - return callback(); - } - - async.waterfall([ - function (next) { - async.parallel({ - hasFlagged: async.apply(Posts.isFlaggedByUser, post.pid, uid), - exists: async.apply(Posts.exists, post.pid), - }, next); - }, - function (results, next) { - if (!results.exists) { - return next(new Error('[[error:no-post]]')); - } - - if (results.hasFlagged) { - return next(new Error('[[error:already-flagged]]')); - } - - var now = Date.now(); - async.parallel([ - function (next) { - db.sortedSetAdd('posts:flagged', now, post.pid, next); - }, - function (next) { - db.sortedSetIncrBy('posts:flags:count', 1, post.pid, next); - }, - function (next) { - db.incrObjectField('post:' + post.pid, 'flags', next); - }, - function (next) { - db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next); - }, - function (next) { - db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next); - }, - function (next) { - if (parseInt(post.uid, 10)) { - async.parallel([ - async.apply(db.sortedSetIncrBy, 'users:flags', 1, post.uid), - async.apply(db.incrObjectField, 'user:' + post.uid, 'flags'), - async.apply(db.sortedSetAdd, 'uid:' + post.uid + ':flag:pids', now, post.pid), - ], next); - } else { - next(); - } - }, - ], next); - }, - function (data, next) { - openNewFlag(post.pid, uid, next); - }, - ], function (err) { - if (err) { - return callback(err); - } - analytics.increment('flags'); - callback(); - }); - }; - - function openNewFlag(pid, uid, callback) { - db.sortedSetScore('posts:flags:count', pid, function (err, count) { - if (err) { - return callback(err); - } - if (count === 1) { // Only update state on new flag - Posts.updateFlagData(uid, pid, { - state: 'open', - }, callback); - } else { - callback(); - } - }); - } - - Posts.isFlaggedByUser = function (pid, uid, callback) { - db.isSortedSetMember('pid:' + pid + ':flag:uids', uid, callback); - }; - - Posts.dismissFlag = function (pid, callback) { - async.waterfall([ - function (next) { - db.getObjectFields('post:' + pid, ['pid', 'uid', 'flags'], next); - }, - function (postData, next) { - if (!postData.pid) { - return callback(); - } - async.parallel([ - function (next) { - if (parseInt(postData.uid, 10)) { - if (parseInt(postData.flags, 10) > 0) { - async.parallel([ - async.apply(db.sortedSetIncrBy, 'users:flags', -postData.flags, postData.uid), - async.apply(db.incrObjectFieldBy, 'user:' + postData.uid, 'flags', -postData.flags), - ], next); - } else { - next(); - } - } else { - next(); - } - }, - function (next) { - db.sortedSetsRemove([ - 'posts:flagged', - 'posts:flags:count', - 'uid:' + postData.uid + ':flag:pids', - ], pid, next); - }, - function (next) { - async.series([ - function (next) { - db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function (err, uids) { - if (err) { - return next(err); - } - - async.each(uids, function (uid, next) { - var nid = 'post_flag:' + pid + ':uid:' + uid; - async.parallel([ - async.apply(db.delete, 'notifications:' + nid), - async.apply(db.sortedSetRemove, 'notifications', 'post_flag:' + pid + ':uid:' + uid), - ], next); - }, next); - }); - }, - async.apply(db.delete, 'pid:' + pid + ':flag:uids'), - ], next); - }, - async.apply(db.deleteObjectField, 'post:' + pid, 'flags'), - async.apply(db.delete, 'pid:' + pid + ':flag:uid:reason'), - async.apply(db.deleteObjectFields, 'post:' + pid, ['flag:state', 'flag:assignee', 'flag:notes', 'flag:history']), - ], next); - }, - function (results, next) { - db.sortedSetsRemoveRangeByScore(['users:flags'], '-inf', 0, next); - }, - ], callback); - }; - - Posts.dismissAllFlags = function (callback) { - db.getSortedSetRange('posts:flagged', 0, -1, function (err, pids) { - if (err) { - return callback(err); - } - async.eachSeries(pids, Posts.dismissFlag, callback); - }); - }; - - Posts.dismissUserFlags = function (uid, callback) { - db.getSortedSetRange('uid:' + uid + ':flag:pids', 0, -1, function (err, pids) { - if (err) { - return callback(err); - } - async.eachSeries(pids, Posts.dismissFlag, callback); - }); - }; - - Posts.getFlags = function (set, cid, uid, start, stop, callback) { - async.waterfall([ - function (next) { - if (Array.isArray(set)) { - db.getSortedSetRevIntersect({ sets: set, start: start, stop: -1, aggregate: 'MAX' }, next); - } else { - db.getSortedSetRevRange(set, start, -1, next); - } - }, - function (pids, next) { - if (cid) { - Posts.filterPidsByCid(pids, cid, next); - } else { - process.nextTick(next, null, pids); - } - }, - function (pids, next) { - getFlaggedPostsWithReasons(pids, uid, next); - }, - function (posts, next) { - var count = posts.length; - var end = stop - start + 1; - next(null, { posts: posts.slice(0, stop === -1 ? undefined : end), count: count }); - }, - ], callback); - }; - - function getFlaggedPostsWithReasons(pids, uid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - uidsReasons: function (next) { - async.map(pids, function (pid, next) { - db.getSortedSetRange('pid:' + pid + ':flag:uid:reason', 0, -1, next); - }, next); - }, - posts: function (next) { - Posts.getPostSummaryByPids(pids, uid, { stripTags: false, extraFields: ['flags', 'flag:assignee', 'flag:state', 'flag:notes', 'flag:history'] }, next); - }, - }, next); - }, - function (results, next) { - async.map(results.uidsReasons, function (uidReasons, next) { - async.map(uidReasons, function (uidReason, next) { - var uid = uidReason.split(':')[0]; - var reason = uidReason.substr(uidReason.indexOf(':') + 1); - user.getUserFields(uid, ['username', 'userslug', 'picture'], function (err, userData) { - next(err, { user: userData, reason: reason }); - }); - }, next); - }, function (err, reasons) { - if (err) { - return callback(err); - } - - results.posts.forEach(function (post, index) { - if (post) { - post.flagReasons = reasons[index]; - } - }); - - next(null, results.posts); - }); - }, - async.apply(Posts.expandFlagHistory), - function (posts, next) { - // Parse out flag data into its own object inside each post hash - async.map(posts, function (postObj, next) { - for (var prop in postObj) { - if (postObj.hasOwnProperty(prop)) { - postObj.flagData = postObj.flagData || {}; - - if (prop.startsWith('flag:')) { - postObj.flagData[prop.slice(5)] = postObj[prop]; - - if (prop === 'flag:state') { - switch (postObj[prop]) { - case 'open': - postObj.flagData.labelClass = 'info'; - break; - case 'wip': - postObj.flagData.labelClass = 'warning'; - break; - case 'resolved': - postObj.flagData.labelClass = 'success'; - break; - case 'rejected': - postObj.flagData.labelClass = 'danger'; - break; - } - } - - delete postObj[prop]; - } - } - } - - if (postObj.flagData.assignee) { - user.getUserFields(parseInt(postObj.flagData.assignee, 10), ['username', 'picture'], function (err, userData) { - if (err) { - return next(err); - } - - postObj.flagData.assigneeUser = userData; - next(null, postObj); - }); - } else { - setImmediate(next.bind(null, null, postObj)); - } - }, next); - }, - ], callback); - } - - Posts.updateFlagData = function (uid, pid, flagObj, callback) { - // Retrieve existing flag data to compare for history-saving purposes - var changes = []; - var changeset = {}; - var prop; - - Posts.getPostData(pid, function (err, postData) { - if (err) { - return callback(err); - } - - // Track new additions - for (prop in flagObj) { - if (flagObj.hasOwnProperty(prop) && !postData.hasOwnProperty('flag:' + prop) && flagObj[prop].length) { - changes.push(prop); - } - } - - // Track changed items - for (prop in postData) { - if ( - postData.hasOwnProperty(prop) && prop.startsWith('flag:') && - flagObj.hasOwnProperty(prop.slice(5)) && - postData[prop] !== flagObj[prop.slice(5)] - ) { - changes.push(prop.slice(5)); - } - } - - changeset = changes.reduce(function (memo, prop) { - memo['flag:' + prop] = flagObj[prop]; - return memo; - }, {}); - - // Append changes to history string - if (changes.length) { - try { - var history = JSON.parse(postData['flag:history'] || '[]'); - - changes.forEach(function (property) { - switch (property) { - case 'assignee': // intentional fall-through - case 'state': - history.unshift({ - uid: uid, - type: property, - value: flagObj[property], - timestamp: Date.now(), - }); - break; - - case 'notes': - history.unshift({ - uid: uid, - type: property, - timestamp: Date.now(), - }); - } - }); - - changeset['flag:history'] = JSON.stringify(history); - } catch (e) { - winston.warn('[posts/updateFlagData] Unable to deserialise post flag history, likely malformed data'); - } - } - - // Save flag data into post hash - if (changes.length) { - Posts.setPostFields(pid, changeset, callback); - } else { - setImmediate(callback); - } - }); - }; - - Posts.expandFlagHistory = function (posts, callback) { - // Expand flag history - async.map(posts, function (post, next) { - var history; - try { - history = JSON.parse(post['flag:history'] || '[]'); - } catch (e) { - winston.warn('[posts/getFlags] Unable to deserialise post flag history, likely malformed data'); - return callback(e); - } - - async.map(history, function (event, next) { - event.timestampISO = new Date(event.timestamp).toISOString(); - - async.parallel([ - function (next) { - user.getUserFields(event.uid, ['username', 'picture'], function (err, userData) { - if (err) { - return next(err); - } - - event.user = userData; - next(); - }); - }, - function (next) { - if (event.type === 'assignee') { - user.getUserField(parseInt(event.value, 10), 'username', function (err, username) { - if (err) { - return next(err); - } - - event.label = username || 'Unknown user'; - next(null); - }); - } else if (event.type === 'state') { - event.label = '[[topic:flag_manage_state_' + event.value + ']]'; - setImmediate(next); - } else { - setImmediate(next); - } - }, - ], function (err) { - next(err, event); - }); - }, function (err, history) { - if (err) { - return next(err); - } - - post['flag:history'] = history; - next(null, post); - }); - }, callback); - }; -}; diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index 23f555f89d..9593d60f09 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -8,12 +8,8 @@ var helpers = {}; helpers.some = function (tasks, callback) { async.some(tasks, function (task, next) { - task(function (err, result) { - next(!err && result); - }); - }, function (result) { - callback(null, result); - }); + task(next); + }, callback); }; helpers.isUserAllowedTo = function (privilege, uid, cid, callback) { diff --git a/src/privileges/users.js b/src/privileges/users.js index ed72efb147..26557a1798 100644 --- a/src/privileges/users.js +++ b/src/privileges/users.js @@ -3,6 +3,7 @@ var async = require('async'); +var user = require('../user'); var groups = require('../groups'); var plugins = require('../plugins'); @@ -157,4 +158,49 @@ module.exports = function (privileges) { callback(null, canEdit); }); }; + + privileges.users.canBanUser = function (callerUid, uid, callback) { + async.waterfall([ + function (next) { + async.parallel({ + isAdmin: function (next) { + privileges.users.isAdministrator(callerUid, next); + }, + isGlobalMod: function (next) { + privileges.users.isGlobalModerator(callerUid, next); + }, + isTargetAdmin: function (next) { + privileges.users.isAdministrator(uid, next); + }, + }, next); + }, + function (results, next) { + results.canBan = !results.isTargetAdmin && (results.isAdmin || results.isGlobalMod); + results.callerUid = callerUid; + results.uid = uid; + plugins.fireHook('filter:user.canBanUser', results, next); + }, + function (data, next) { + next(null, data.canBan); + }, + ], callback); + }; + + privileges.users.hasBanPrivilege = function (uid, callback) { + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(uid, next); + }, + function (isAdminOrGlobalMod, next) { + plugins.fireHook('filter:user.hasBanPrivilege', { + uid: uid, + isAdminOrGlobalMod: isAdminOrGlobalMod, + canBan: isAdminOrGlobalMod, + }, next); + }, + function (data, next) { + next(null, data.canBan); + }, + ], callback); + }; }; diff --git a/src/rewards/index.js b/src/rewards/index.js index 38105dd5ff..578ba5af2d 100644 --- a/src/rewards/index.js +++ b/src/rewards/index.js @@ -33,12 +33,14 @@ rewards.checkConditionAndRewardUser = function (uid, condition, method, callback async.filter(rewards, function (reward, next) { if (!reward) { - return next(false); + return next(null, false); } - checkCondition(reward, method, next); - }, function (eligible) { - if (!eligible) { + checkCondition(reward, method, function (result) { + next(null, result); + }); + }, function (err, eligible) { + if (err || !eligible) { return next(false); } diff --git a/src/routes/admin.js b/src/routes/admin.js index 35ad84203d..42fd935aa7 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -56,7 +56,6 @@ function addRoutes(router, middleware, controllers) { router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics); router.get('/manage/tags', middlewares, controllers.admin.tags.get); - router.get('/manage/flags', middlewares, controllers.admin.flags.get); router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get); router.get('/manage/users', middlewares, controllers.admin.users.sortByJoinDate); diff --git a/src/routes/helpers.js b/src/routes/helpers.js index 8452b7f6d9..0aae67cbec 100644 --- a/src/routes/helpers.js +++ b/src/routes/helpers.js @@ -3,10 +3,15 @@ var helpers = {}; helpers.setupPageRoute = function (router, name, middleware, middlewares, controller) { - middlewares = middlewares.concat([middleware.maintenanceMode, middleware.registrationComplete, middleware.pageView, middleware.pluginHooks]); + middlewares = [middleware.maintenanceMode, middleware.registrationComplete, middleware.pageView, middleware.pluginHooks].concat(middlewares); router.get(name, middleware.busyCheck, middleware.buildHeader, middlewares, controller); router.get('/api' + name, middlewares, controller); }; +helpers.setupAdminPageRoute = function (router, name, middleware, middlewares, controller) { + router.get(name, middleware.admin.buildHeader, middlewares, controller); + router.get('/api' + name, middlewares, controller); +}; + module.exports = helpers; diff --git a/src/routes/index.js b/src/routes/index.js index 57b1d5c942..3dc6047b22 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -34,13 +34,11 @@ function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/search', middleware, [], controllers.search.search); setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); - - app.get('/ping', controllers.ping); - app.get('/sping', controllers.ping); } function modRoutes(app, middleware, controllers) { - setupPageRoute(app, '/posts/flags', middleware, [], controllers.mods.flagged); + setupPageRoute(app, '/flags', middleware, [], controllers.mods.flags.list); + setupPageRoute(app, '/flags/:flagId', middleware, [], controllers.mods.flags.detail); } function globalModRoutes(app, middleware, controllers) { diff --git a/src/search.js b/src/search.js index e223e3ea14..d3256ef90b 100644 --- a/src/search.js +++ b/src/search.js @@ -146,7 +146,7 @@ function getMatchedPosts(pids, data, callback) { topicFields.push('postcount'); } - if (data.sortBy) { + if (data.sortBy && data.sortBy !== 'relevance') { if (data.sortBy.startsWith('category')) { topicFields.push('cid'); } else if (data.sortBy.startsWith('topic.')) { @@ -326,7 +326,7 @@ function filterByTags(posts, hasTags) { } function sortPosts(posts, data) { - if (!posts.length || !data.sortBy) { + if (!posts.length || !data.sortBy || data.sortBy === 'relevance') { return; } diff --git a/src/sitemap.js b/src/sitemap.js index d828f9d784..8ed9971e7b 100644 --- a/src/sitemap.js +++ b/src/sitemap.js @@ -19,31 +19,25 @@ var sitemap = { }; sitemap.render = function (callback) { - var numTopics = parseInt(meta.config.sitemapTopics, 10) || 500; + var topicsPerPage = parseInt(meta.config.sitemapTopics, 10) || 500; var returnData = { url: nconf.get('url'), topics: [], }; - var numPages; async.waterfall([ - async.apply(db.getSortedSetRange, 'topics:recent', 0, -1), - function (tids, next) { - privileges.topics.filterTids('read', tids, 0, next); + function (next) { + db.getObjectField('global', 'topicCount', next); }, - ], function (err, tids) { - if (err) { - numPages = 1; - } else { - numPages = Math.ceil(tids.length / numTopics); - } - - for (var x = 1; x <= numPages; x += 1) { - returnData.topics.push(x); - } + function (topicCount, next) { + var numPages = Math.max(0, topicCount / topicsPerPage); + for (var x = 1; x <= numPages; x += 1) { + returnData.topics.push(x); + } - callback(null, returnData); - }); + next(null, returnData); + }, + ], callback); }; sitemap.getPages = function (callback) { diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 9eba77f39b..1b4d9ebada 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -173,6 +173,7 @@ SocketAdmin.config.setMultiple = function (socket, data, callback) { logger.monitorConfig({ io: index.server }, setting); } } + plugins.fireHook('action:config.set', { settings: data }); setImmediate(next); }, ], callback); diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js index 50c2061038..d4e8ef1414 100644 --- a/src/socket.io/admin/categories.js +++ b/src/socket.io/admin/categories.js @@ -7,7 +7,9 @@ var groups = require('../../groups'); var categories = require('../../categories'); var privileges = require('../../privileges'); var plugins = require('../../plugins'); -var Categories = {}; +var events = require('../../events'); + +var Categories = module.exports; Categories.create = function (socket, data, callback) { if (!data) { @@ -42,7 +44,26 @@ Categories.getNames = function (socket, data, callback) { }; Categories.purge = function (socket, cid, callback) { - categories.purge(cid, socket.uid, callback); + var name; + async.waterfall([ + function (next) { + categories.getCategoryField(cid, 'name', next); + }, + function (_name, next) { + name = _name; + categories.purge(cid, socket.uid, next); + }, + function (next) { + events.log({ + type: 'category-purge', + uid: socket.uid, + ip: socket.ip, + cid: cid, + name: name, + }); + setImmediate(next); + }, + ], callback); }; Categories.update = function (socket, data, callback) { @@ -102,5 +123,3 @@ Categories.copySettingsFrom = function (socket, data, callback) { Categories.copyPrivilegesFrom = function (socket, data, callback) { categories.copyPrivilegesFrom(data.fromCid, data.toCid, callback); }; - -module.exports = Categories; diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 8770381aae..cf002001cb 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -69,14 +69,6 @@ User.resetLockouts = function (socket, uids, callback) { async.each(uids, user.auth.resetLockout, callback); }; -User.resetFlags = function (socket, uids, callback) { - if (!Array.isArray(uids)) { - return callback(new Error('[[error:invalid-data]]')); - } - - user.resetFlags(uids, callback); -}; - User.validateEmail = function (socket, uids, callback) { if (!Array.isArray(uids)) { return callback(new Error('[[error:invalid-data]]')); diff --git a/src/socket.io/flags.js b/src/socket.io/flags.js new file mode 100644 index 0000000000..2cc2881832 --- /dev/null +++ b/src/socket.io/flags.js @@ -0,0 +1,102 @@ +'use strict'; + +var async = require('async'); + +var user = require('../user'); +var flags = require('../flags'); + +var SocketFlags = {}; + +SocketFlags.create = function (socket, data, callback) { + if (!socket.uid) { + return callback(new Error('[[error:not-logged-in]]')); + } + + if (!data || !data.type || !data.id || !data.reason) { + return callback(new Error('[[error:invalid-data]]')); + } + + async.waterfall([ + async.apply(flags.validate, { + uid: socket.uid, + type: data.type, + id: data.id, + }), + function (next) { + // If we got here, then no errors occurred + flags.create(data.type, data.id, socket.uid, data.reason, next); + }, + ], function (err, flagObj) { + if (err) { + return callback(err); + } + + flags.notify(flagObj, socket.uid); + callback(null, flagObj); + }); +}; + +SocketFlags.update = function (socket, data, callback) { + if (!data || !(data.flagId && data.data)) { + return callback(new Error('[[error:invalid-data]]')); + } + + var payload = {}; + + async.waterfall([ + function (next) { + async.parallel([ + async.apply(user.isAdminOrGlobalMod, socket.uid), + async.apply(user.isModeratorOfAnyCategory, socket.uid), + ], function (err, results) { + next(err, results[0] || results[1]); + }); + }, + function (allowed, next) { + if (!allowed) { + return next(new Error('[[no-privileges]]')); + } + + // Translate form data into object + payload = data.data.reduce(function (memo, cur) { + memo[cur.name] = cur.value; + return memo; + }, payload); + + flags.update(data.flagId, socket.uid, payload, next); + }, + async.apply(flags.getHistory, data.flagId), + ], callback); +}; + +SocketFlags.appendNote = function (socket, data, callback) { + if (!data || !(data.flagId && data.note)) { + return callback(new Error('[[error:invalid-data]]')); + } + + async.waterfall([ + function (next) { + async.parallel([ + async.apply(user.isAdminOrGlobalMod, socket.uid), + async.apply(user.isModeratorOfAnyCategory, socket.uid), + ], function (err, results) { + next(err, results[0] || results[1]); + }); + }, + function (allowed, next) { + if (!allowed) { + return next(new Error('[[no-privileges]]')); + } + + flags.appendNote(data.flagId, socket.uid, data.note, next); + }, + function (next) { + async.parallel({ + notes: async.apply(flags.getNotes, data.flagId), + history: async.apply(flags.getHistory, data.flagId), + }, next); + }, + ], callback); +}; + +module.exports = SocketFlags; diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index ce2ee7b30c..241937d5fb 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -106,6 +106,7 @@ SocketHelpers.sendNotificationToPostOwner = function (pid, fromuid, command, not var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); notifications.create({ + type: command, bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]', bodyLong: results.postObj.content, pid: pid, diff --git a/src/socket.io/index.js b/src/socket.io/index.js index ccabf242fa..00d3433a3d 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -151,7 +151,7 @@ function onMessage(socket, payload) { function requireModules() { var modules = ['admin', 'categories', 'groups', 'meta', 'modules', - 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist', + 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist', 'flags', ]; modules.forEach(function (module) { @@ -219,7 +219,11 @@ function addRedisAdapter(io) { var redis = require('../database/redis'); var pub = redis.connect(); var sub = redis.connect(); - io.adapter(redisAdapter({ pubClient: pub, subClient: sub })); + io.adapter(redisAdapter({ + key: 'db:' + nconf.get('redis:database') + ':adapter_key', + pubClient: pub, + subClient: sub, + })); } else if (nconf.get('isCluster') === 'true') { winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.'); } diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 4480a9d0f7..a3a9ff4fe3 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -3,6 +3,7 @@ var async = require('async'); var validator = require('validator'); +var db = require('../database'); var meta = require('../meta'); var notifications = require('../notifications'); var plugins = require('../plugins'); @@ -36,6 +37,17 @@ SocketModules.chats.getRaw = function (socket, data, callback) { ], callback); }; +SocketModules.chats.isDnD = function (socket, uid, callback) { + async.waterfall([ + function (next) { + db.getObjectField('user:' + uid, 'status', next); + }, + function (status, next) { + next(null, status === 'dnd'); + }, + ], callback); +}; + SocketModules.chats.newRoom = function (socket, data, callback) { if (!data) { return callback(new Error('[[error:invalid-data]]')); @@ -133,6 +145,7 @@ SocketModules.chats.loadRoom = function (socket, data, callback) { results.roomData.groupChat = results.roomData.hasOwnProperty('groupChat') ? results.roomData.groupChat : results.users.length > 2; results.roomData.isOwner = parseInt(results.roomData.owner, 10) === socket.uid; results.roomData.maximumUsersInChatRoom = parseInt(meta.config.maximumUsersInChatRoom, 10) || 0; + results.roomData.maximumChatMessageLength = parseInt(meta.config.maximumChatMessageLength, 10) || 1000; results.roomData.showUserInput = !results.roomData.maximumUsersInChatRoom || results.roomData.maximumUsersInChatRoom > 2; next(null, results.roomData); }, diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js index af47782d5d..80f2bb4e12 100644 --- a/src/socket.io/notifications.js +++ b/src/socket.io/notifications.js @@ -1,11 +1,8 @@ 'use strict'; -var async = require('async'); var user = require('../user'); var notifications = require('../notifications'); -var utils = require('../utils'); - -var SocketNotifs = {}; +var SocketNotifs = module.exports; SocketNotifs.get = function (socket, data, callback) { if (data && Array.isArray(data.nids) && socket.uid) { @@ -15,25 +12,6 @@ SocketNotifs.get = function (socket, data, callback) { } }; -SocketNotifs.loadMore = function (socket, data, callback) { - if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { - return callback(new Error('[[error:invalid-data]]')); - } - if (!socket.uid) { - return callback(new Error('[[error:no-privileges]]')); - } - var start = parseInt(data.after, 10); - var stop = start + 20; - async.waterfall([ - function (next) { - user.notifications.getAll(socket.uid, start, stop, next); - }, - function (notifications, next) { - next(null, { notifications: notifications, nextStart: stop }); - }, - ], callback); -}; - SocketNotifs.getCount = function (socket, data, callback) { user.notifications.getUnreadCount(socket.uid, callback); }; @@ -57,5 +35,3 @@ SocketNotifs.markUnread = function (socket, nid, callback) { SocketNotifs.markAllRead = function (socket, data, callback) { notifications.markAllRead(socket.uid, callback); }; - -module.exports = SocketNotifs; diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index e98052a2f9..07e24df082 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -20,7 +20,6 @@ require('./posts/move')(SocketPosts); require('./posts/votes')(SocketPosts); require('./posts/bookmarks')(SocketPosts); require('./posts/tools')(SocketPosts); -require('./posts/flag')(SocketPosts); SocketPosts.reply = function (socket, data, callback) { if (!data || !data.tid || !data.content) { diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js deleted file mode 100644 index 1464c6ac93..0000000000 --- a/src/socket.io/posts/flag.js +++ /dev/null @@ -1,171 +0,0 @@ -'use strict'; - -var async = require('async'); -var S = require('string'); - -var user = require('../../user'); -var groups = require('../../groups'); -var posts = require('../../posts'); -var topics = require('../../topics'); -var privileges = require('../../privileges'); -var notifications = require('../../notifications'); -var plugins = require('../../plugins'); -var meta = require('../../meta'); -var utils = require('../../utils'); - -module.exports = function (SocketPosts) { - SocketPosts.flag = function (socket, data, callback) { - if (!socket.uid) { - return callback(new Error('[[error:not-logged-in]]')); - } - - if (!data || !data.pid || !data.reason) { - return callback(new Error('[[error:invalid-data]]')); - } - - var flaggingUser = {}; - var post; - - async.waterfall([ - function (next) { - posts.getPostFields(data.pid, ['pid', 'tid', 'uid', 'content', 'deleted'], next); - }, - function (postData, next) { - if (parseInt(postData.deleted, 10) === 1) { - return next(new Error('[[error:post-deleted]]')); - } - - post = postData; - topics.getTopicFields(post.tid, ['title', 'cid'], next); - }, - function (topicData, next) { - post.topic = topicData; - - async.parallel({ - isAdminOrMod: function (next) { - privileges.categories.isAdminOrMod(post.topic.cid, socket.uid, next); - }, - userData: function (next) { - user.getUserFields(socket.uid, ['username', 'reputation', 'banned'], next); - }, - }, next); - }, - function (user, next) { - var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1; - if (!user.isAdminOrMod && parseInt(user.userData.reputation, 10) < minimumReputation) { - return next(new Error('[[error:not-enough-reputation-to-flag]]')); - } - - if (parseInt(user.banned, 10) === 1) { - return next(new Error('[[error:user-banned]]')); - } - - flaggingUser = user.userData; - flaggingUser.uid = socket.uid; - - posts.flag(post, socket.uid, data.reason, next); - }, - function (next) { - async.parallel({ - post: function (next) { - posts.parsePost(post, next); - }, - admins: function (next) { - groups.getMembers('administrators', 0, -1, next); - }, - globalMods: function (next) { - groups.getMembers('Global Moderators', 0, -1, next); - }, - moderators: function (next) { - groups.getMembers('cid:' + post.topic.cid + ':privileges:mods', 0, -1, next); - }, - }, next); - }, - function (results, next) { - var title = S(post.topic.title).decodeHTMLEntities().s; - var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); - - notifications.create({ - bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + titleEscaped + ']]', - bodyLong: post.content, - pid: data.pid, - path: '/post/' + data.pid, - nid: 'post_flag:' + data.pid + ':uid:' + socket.uid, - from: socket.uid, - mergeId: 'notifications:user_flagged_post_in|' + data.pid, - topicTitle: post.topic.title, - }, function (err, notification) { - if (err || !notification) { - return next(err); - } - - plugins.fireHook('action:post.flag', { post: post, reason: data.reason, flaggingUser: flaggingUser }); - notifications.push(notification, results.admins.concat(results.moderators).concat(results.globalMods), next); - }); - }, - ], callback); - }; - - SocketPosts.dismissFlag = function (socket, pid, callback) { - if (!pid || !socket.uid) { - return callback(new Error('[[error:invalid-data]]')); - } - async.waterfall([ - function (next) { - user.isAdminOrGlobalMod(socket.uid, next); - }, - function (isAdminOrGlobalModerator, next) { - if (!isAdminOrGlobalModerator) { - return next(new Error('[[no-privileges]]')); - } - posts.dismissFlag(pid, next); - }, - ], callback); - }; - - SocketPosts.dismissAllFlags = function (socket, data, callback) { - async.waterfall([ - function (next) { - user.isAdminOrGlobalMod(socket.uid, next); - }, - function (isAdminOrGlobalModerator, next) { - if (!isAdminOrGlobalModerator) { - return next(new Error('[[no-privileges]]')); - } - posts.dismissAllFlags(next); - }, - ], callback); - }; - - SocketPosts.updateFlag = function (socket, data, callback) { - if (!data || !(data.pid && data.data)) { - return callback(new Error('[[error:invalid-data]]')); - } - - var payload = {}; - - async.waterfall([ - function (next) { - async.parallel([ - async.apply(user.isAdminOrGlobalMod, socket.uid), - async.apply(user.isModeratorOfAnyCategory, socket.uid), - ], function (err, results) { - next(err, results[0] || results[1]); - }); - }, - function (allowed, next) { - if (!allowed) { - return next(new Error('[[no-privileges]]')); - } - - // Translate form data into object - payload = data.data.reduce(function (memo, cur) { - memo[cur.name] = cur.value; - return memo; - }, payload); - - posts.updateFlagData(socket.uid, data.pid, payload, next); - }, - ], callback); - }; -}; diff --git a/src/socket.io/posts/helpers.js b/src/socket.io/posts/helpers.js index c7b92488d5..a9bb9b451d 100644 --- a/src/socket.io/posts/helpers.js +++ b/src/socket.io/posts/helpers.js @@ -14,10 +14,14 @@ helpers.postCommand = function (socket, command, eventName, notification, data, return callback(new Error('[[error:not-logged-in]]')); } - if (!data || !data.pid || !data.room_id) { + if (!data || !data.pid) { return callback(new Error('[[error:invalid-data]]')); } + if (!data.room_id) { + return callback(new Error('[[error:invalid-room-id, ' + data.room_id + ' ]]')); + } + async.waterfall([ function (next) { async.parallel({ diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index c1ad05b119..c075a96a8e 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -138,39 +138,46 @@ module.exports = function (SocketPosts) { return callback(new Error('[[error:invalid-data]]')); } var postData; + var topicData; + var isMainAndLast = false; async.waterfall([ function (next) { isMainAndLastPost(data.pid, next); }, function (results, next) { if (results.isMain && !results.isLast) { - return callback(new Error('[[error:cant-purge-main-post]]')); + return next(new Error('[[error:cant-purge-main-post]]')); } - if (results.isMain && results.isLast) { - return deleteTopicOf(data.pid, socket, next); - } - setImmediate(next); - }, - function (next) { - posts.getPostField(data.pid, 'toPid', next); + isMainAndLast = results.isMain && results.isLast; + + posts.getPostFields(data.pid, ['toPid', 'tid'], next); }, - function (toPid, next) { - postData = { pid: data.pid, toPid: toPid }; + function (_postData, next) { + postData = _postData; + postData.pid = data.pid; posts.tools.purge(socket.uid, data.pid, next); }, function (next) { websockets.in('topic_' + data.tid).emit('event:post_purged', postData); - topics.getTopicField(data.tid, 'title', next); + topics.getTopicFields(data.tid, ['title', 'cid'], next); }, - function (title, next) { + function (_topicData, next) { + topicData = _topicData; events.log({ type: 'post-purge', uid: socket.uid, pid: data.pid, ip: socket.ip, - title: String(title), + title: String(topicData.title), }, next); }, + function (next) { + if (isMainAndLast) { + socketTopics.doTopicAction('purge', 'event:topic_purged', socket, { tids: [postData.tid], cid: topicData.cid }, next); + } else { + setImmediate(next); + } + }, ], callback); }; diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index 87b27a6561..f0f6cd2ba6 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -40,19 +40,19 @@ module.exports = function (SocketTopics) { var reverse = data.topicPostSort === 'newest_to_oldest' || data.topicPostSort === 'most_votes'; var start = Math.max(0, parseInt(data.after, 10)); - var infScrollPostsPerPage = 10; + var infScrollPostsPerPage = Math.max(0, Math.min(meta.config.postsPerPage || 20, parseInt(data.postsPerPage, 10) || meta.config.postsPerPage || 20) - 1); if (data.direction > 0) { if (reverse) { start = results.topic.postcount - start; } } else if (reverse) { - start = results.topic.postcount - start - infScrollPostsPerPage - 1; + start = results.topic.postcount - start - infScrollPostsPerPage; } else { - start = start - infScrollPostsPerPage - 1; + start -= infScrollPostsPerPage; } - var stop = start + (infScrollPostsPerPage - 1); + var stop = start + (infScrollPostsPerPage); start = Math.max(0, start); stop = Math.max(0, stop); @@ -93,9 +93,9 @@ module.exports = function (SocketTopics) { } var start = parseInt(data.after, 10); - var stop = start + 9; + var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage || 20, parseInt(data.topicsPerPage, 10) || meta.config.topicsPerPage || 20) - 1); - topics.getUnreadTopics(data.cid, socket.uid, start, stop, data.filter, callback); + topics.getUnreadTopics({ cid: data.cid, uid: socket.uid, start: start, stop: stop, filter: data.filter }, callback); }; SocketTopics.loadMoreRecentTopics = function (socket, data, callback) { @@ -104,7 +104,7 @@ module.exports = function (SocketTopics) { } var start = parseInt(data.after, 10); - var stop = start + 9; + var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage || 20, parseInt(data.topicsPerPage, 10) || meta.config.topicsPerPage || 20) - 1); topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback); }; @@ -115,7 +115,7 @@ module.exports = function (SocketTopics) { } var start = parseInt(data.after, 10); - var stop = start + 9; + var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage || 20, parseInt(data.topicsPerPage, 10) || meta.config.topicsPerPage || 20) - 1); topics.getTopicsFromSet(data.set, socket.uid, start, stop, callback); }; diff --git a/src/socket.io/topics/unread.js b/src/socket.io/topics/unread.js index 8fa5651e32..29ebe8b189 100644 --- a/src/socket.io/topics/unread.js +++ b/src/socket.io/topics/unread.js @@ -50,7 +50,7 @@ module.exports = function (SocketTopics) { SocketTopics.markCategoryTopicsRead = function (socket, cid, callback) { async.waterfall([ function (next) { - topics.getUnreadTids(cid, socket.uid, '', next); + topics.getUnreadTids({ cid: cid, uid: socket.uid, filter: '' }, next); }, function (tids, next) { SocketTopics.markAsRead(socket, tids, next); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 78f696a19b..a2e4413c9a 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -171,6 +171,7 @@ SocketUser.follow = function (socket, data, callback) { function (_userData, next) { userData = _userData; notifications.create({ + type: 'follow', bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', nid: 'follow:' + data.uid + ':uid:' + socket.uid, from: socket.uid, @@ -256,6 +257,7 @@ SocketUser.getUnreadCounts = function (socket, data, callback) { async.parallel({ unreadTopicCount: async.apply(topics.getTotalUnread, socket.uid), unreadNewTopicCount: async.apply(topics.getTotalUnread, socket.uid, 'new'), + unreadWatchedTopicCount: async.apply(topics.getTotalUnread, socket.uid, 'watched'), unreadChatCount: async.apply(messaging.getUnreadCount, socket.uid), unreadNotificationCount: async.apply(user.notifications.getUnreadCount, socket.uid), }, callback); @@ -315,7 +317,7 @@ SocketUser.getUserByEmail = function (socket, email, callback) { }; SocketUser.setModerationNote = function (socket, data, callback) { - if (!socket.uid || !data || !data.uid) { + if (!socket.uid || !data || !data.uid || !data.note) { return callback(new Error('[[error:invalid-data]]')); } @@ -334,11 +336,13 @@ SocketUser.setModerationNote = function (socket, data, callback) { if (!allowed) { return next(new Error('[[error:no-privileges]]')); } - if (data.note) { - user.setUserField(data.uid, 'moderationNote', data.note, next); - } else { - db.deleteObjectField('user:' + data.uid, 'moderationNote', next); - } + + var note = { + uid: socket.uid, + note: data.note, + timestamp: Date.now(), + }; + db.sortedSetAdd('uid:' + data.uid + ':moderation:notes', note.timestamp, JSON.stringify(note), next); }, ], callback); }; diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index 54ce94fd24..191b98767e 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -1,11 +1,15 @@ 'use strict'; var async = require('async'); + var user = require('../../user'); +var meta = require('../../meta'); var websockets = require('../index'); var events = require('../../events'); - +var privileges = require('../../privileges'); var plugins = require('../../plugins'); +var emailer = require('../../emailer'); +var translator = require('../../translator'); module.exports = function (SocketUser) { SocketUser.banUsers = function (socket, data, callback) { @@ -35,6 +39,9 @@ module.exports = function (SocketUser) { }); next(); }, + function (next) { + user.auth.revokeAllSessions(uid, next); + }, ], next); }, callback); }; @@ -72,10 +79,10 @@ module.exports = function (SocketUser) { async.waterfall([ function (next) { - user.isAdminOrGlobalMod(uid, next); + privileges.users.hasBanPrivilege(uid, next); }, - function (isAdminOrGlobalMod, next) { - if (!isAdminOrGlobalMod) { + function (hasBanPrivilege, next) { + if (!hasBanPrivilege) { return next(new Error('[[error:no-privileges]]')); } async.each(uids, method, next); @@ -92,10 +99,38 @@ module.exports = function (SocketUser) { if (isAdmin) { return next(new Error('[[error:cant-ban-other-admins]]')); } + + user.getUserField(uid, 'username', next); + }, + function (username, next) { + var siteTitle = meta.config.title || 'NodeBB'; + var data = { + subject: '[[email:banned.subject, ' + siteTitle + ']]', + site_title: siteTitle, + username: username, + until: until ? new Date(until).toString() : false, + reason: reason, + }; + + emailer.send('banned', uid, data, next); + }, + function (next) { user.ban(uid, until, reason, next); }, function (next) { - websockets.in('uid_' + uid).emit('event:banned'); + if (!reason) { + return translator.translate('[[user:info.banned-no-reason]]', function (translated) { + next(false, translated); + }); + } + + next(false, reason); + }, + function (_reason, next) { + websockets.in('uid_' + uid).emit('event:banned', { + until: until, + reason: _reason, + }); next(); }, ], callback); diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js index 109b2635e1..3723699461 100644 --- a/src/socket.io/user/picture.js +++ b/src/socket.io/user/picture.js @@ -99,6 +99,9 @@ module.exports = function (SocketUser) { picture: userData.uploadedpicture === userData.picture ? '' : userData.picture, // if current picture is uploaded picture, reset to user icon }, next); }, + function (next) { + plugins.fireHook('action:user.removeUploadedPicture', { callerUid: socket.uid, uid: data.uid }, next); + }, ], callback); }; diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js index 8e88edb1e5..7f6a9de2f9 100644 --- a/src/socket.io/user/profile.js +++ b/src/socket.io/user/profile.js @@ -161,7 +161,7 @@ module.exports = function (SocketUser) { data.email = oldUserData.email; } - user.updateProfile(data.uid, data, next); + user.updateProfile(socket.uid, data, next); }, function (userData, next) { function log(type, eventData) { diff --git a/src/start.js b/src/start.js index 96025c726c..8d4e465a55 100644 --- a/src/start.js +++ b/src/start.js @@ -48,7 +48,7 @@ start.start = function () { require('./socket.io').init(webserver.server); if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) { - require('./notifications').init(); + require('./notifications').startJobs(); require('./user').startJobs(); } @@ -58,16 +58,16 @@ start.start = function () { if (err) { switch (err.message) { case 'schema-out-of-date': - winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:'); - winston.warn(' ./nodebb upgrade'); + winston.error('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:'); + winston.error(' ./nodebb upgrade'); break; case 'dependencies-out-of-date': - winston.warn('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:'); - winston.warn(' ./nodebb upgrade'); + winston.error('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:'); + winston.error(' ./nodebb upgrade'); break; case 'dependencies-missing': - winston.warn('One or more of NodeBB\'s dependent packages are missing. Please run the following command to update them:'); - winston.warn(' ./nodebb upgrade'); + winston.error('One or more of NodeBB\'s dependent packages are missing. Please run the following command to update them:'); + winston.error(' ./nodebb upgrade'); break; default: winston.error(err); diff --git a/src/topics/create.js b/src/topics/create.js index 827465478b..2ec75d3781 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -2,6 +2,7 @@ 'use strict'; var async = require('async'); +var _ = require('underscore'); var validator = require('validator'); var S = require('string'); var db = require('../database'); @@ -81,7 +82,7 @@ module.exports = function (Topics) { ], next); }, function (results, next) { - plugins.fireHook('action:topic.save', topicData); + plugins.fireHook('action:topic.save', { topic: _.clone(topicData) }); next(null, topicData.tid); }, ], callback); @@ -177,7 +178,7 @@ module.exports = function (Topics) { data.postData.index = 0; analytics.increment(['topics', 'topics:byCid:' + data.topicData.cid]); - plugins.fireHook('action:topic.post', data.topicData); + plugins.fireHook('action:topic.post', { topic: data.topicData, post: data.postData }); if (parseInt(uid, 10)) { user.notifications.sendTopicNotificationToFollowers(uid, data.topicData, data.postData); @@ -272,7 +273,7 @@ module.exports = function (Topics) { Topics.notifyFollowers(postData, uid); analytics.increment(['posts', 'posts:byCid:' + cid]); - plugins.fireHook('action:topic.reply', postData); + plugins.fireHook('action:topic.reply', { post: _.clone(postData) }); next(null, postData); }, diff --git a/src/topics/delete.js b/src/topics/delete.js index 082244f96d..67e8bdf230 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -154,7 +154,10 @@ module.exports = function (Topics) { }); }, function (next) { - plugins.fireHook('action:topic.purge', tid); + Topics.getTopicData(tid, next); + }, + function (topicData, next) { + plugins.fireHook('action:topic.purge', { topic: topicData, uid: uid }); db.delete('topic:' + tid, next); }, ], callback); diff --git a/src/topics/follow.js b/src/topics/follow.js index a3b1041b13..494872c794 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -162,27 +162,31 @@ module.exports = function (Topics) { }; Topics.filterWatchedTids = function (tids, uid, callback) { - db.sortedSetScores('uid:' + uid + ':followed_tids', tids, function (err, scores) { - if (err) { - return callback(err); - } - tids = tids.filter(function (tid, index) { - return tid && !!scores[index]; - }); - callback(null, tids); - }); + async.waterfall([ + function (next) { + db.sortedSetScores('uid:' + uid + ':followed_tids', tids, next); + }, + function (scores, next) { + tids = tids.filter(function (tid, index) { + return tid && !!scores[index]; + }); + next(null, tids); + }, + ], callback); }; Topics.filterNotIgnoredTids = function (tids, uid, callback) { - db.sortedSetScores('uid:' + uid + ':ignored_tids', tids, function (err, scores) { - if (err) { - return callback(err); - } - tids = tids.filter(function (tid, index) { - return tid && !scores[index]; - }); - callback(null, tids); - }); + async.waterfall([ + function (next) { + db.sortedSetScores('uid:' + uid + ':ignored_tids', tids, next); + }, + function (scores, next) { + tids = tids.filter(function (tid, index) { + return tid && !scores[index]; + }); + next(null, tids); + }, + ], callback); }; Topics.notifyFollowers = function (postData, exceptUid, callback) { @@ -224,6 +228,7 @@ module.exports = function (Topics) { postData.content = posts.relativeToAbsolute(postData.content); notifications.create({ + type: 'new-reply', bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]', bodyLong: postData.content, pid: postData.pid, diff --git a/src/topics/fork.js b/src/topics/fork.js index 396ae3d63b..5edd9f55f4 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -9,7 +9,6 @@ var privileges = require('../privileges'); var plugins = require('../plugins'); var meta = require('../meta'); - module.exports = function (Topics) { Topics.createTopicFromPosts = function (uid, title, pids, fromTid, callback) { if (title) { @@ -57,7 +56,8 @@ module.exports = function (Topics) { Topics.updateTopicBookmarks(fromTid, pids, function () { next(null, results); }); }, function (_tid, next) { - function move(pid, next) { + tid = _tid; + async.eachSeries(pids, function (pid, next) { privileges.posts.canEdit(pid, uid, function (err, canEdit) { if (err || !canEdit.flag) { return next(err || new Error(canEdit.message)); @@ -65,14 +65,13 @@ module.exports = function (Topics) { Topics.movePostToTopic(pid, tid, next); }); - } - tid = _tid; - async.eachSeries(pids, move, next); + }, next); }, function (next) { Topics.updateTimestamp(tid, Date.now(), next); }, function (next) { + plugins.fireHook('action:topic.fork', { tid: tid, fromTid: fromTid, uid: uid }); Topics.getTopicData(tid, next); }, ], callback); diff --git a/src/topics/posts.js b/src/topics/posts.js index a1bf496083..ce2b6dd457 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -10,6 +10,7 @@ var user = require('../user'); var posts = require('../posts'); var meta = require('../meta'); var plugins = require('../plugins'); +var utils = require('../../public/src/utils'); module.exports = function (Topics) { Topics.onNewPostMade = function (postData, callback) { @@ -103,6 +104,9 @@ module.exports = function (Topics) { parents: function (next) { Topics.addParentPosts(postData, next); }, + replies: function (next) { + getPostReplies(pids, uid, next); + }, }, next); }, function (results, next) { @@ -115,7 +119,7 @@ module.exports = function (Topics) { postObj.upvoted = results.voteData.upvotes[i]; postObj.downvoted = results.voteData.downvotes[i]; postObj.votes = postObj.votes || 0; - postObj.replies = postObj.replies || 0; + postObj.replies = results.replies[i]; postObj.selfPost = !!parseInt(uid, 10) && parseInt(uid, 10) === parseInt(postObj.uid, 10); // Username override for guests, if enabled @@ -385,4 +389,53 @@ module.exports = function (Topics) { Topics.getPostCount = function (tid, callback) { db.getObjectField('topic:' + tid, 'postcount', callback); }; + + function getPostReplies(pids, callerUid, callback) { + async.map(pids, function (pid, next) { + db.getSortedSetRange('pid:' + pid + ':replies', 0, -1, function (err, replyPids) { + if (err) { + return next(err); + } + + var uids = []; + var count = 0; + + async.until(function () { + return count === replyPids.length || uids.length === 6; + }, function (next) { + posts.getPostField(replyPids[count], 'uid', function (err, uid) { + uid = parseInt(uid, 10); + if (uids.indexOf(uid) === -1) { + uids.push(uid); + } + count += 1; + next(err); + }); + }, function (err) { + if (err) { + return next(err); + } + + async.parallel({ + users: function (next) { + user.getUsersWithFields(uids, ['uid', 'username', 'userslug', 'picture'], callerUid, next); + }, + timestampISO: function (next) { + posts.getPostField(replyPids[0], 'timestamp', function (err, timestamp) { + next(err, utils.toISOString(timestamp)); + }); + }, + }, function (err, replies) { + if (replies.users.length > 5) { + replies.users.shift(); + replies.hasMore = true; + } + + replies.count = replyPids.length; + next(err, replies); + }); + }); + }); + }, callback); + } }; diff --git a/src/topics/recent.js b/src/topics/recent.js index dbecd035b4..6801c0095b 100644 --- a/src/topics/recent.js +++ b/src/topics/recent.js @@ -8,6 +8,7 @@ var plugins = require('../plugins'); var privileges = require('../privileges'); var user = require('../user'); var categories = require('../categories'); +var meta = require('../meta'); module.exports = function (Topics) { var terms = { @@ -65,7 +66,7 @@ module.exports = function (Topics) { function (tids, next) { async.parallel({ ignoredCids: function (next) { - if (filter === 'watched') { + if (filter === 'watched' || parseInt(meta.config.disableRecentCategoryFilter, 10) === 1) { return next(null, []); } user.getIgnoredCategories(uid, next); diff --git a/src/topics/tools.js b/src/topics/tools.js index f83a5f91f0..d44f259ba7 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -55,9 +55,9 @@ module.exports = function (Topics) { topicData.deleted = isDelete ? 1 : 0; if (isDelete) { - plugins.fireHook('action:topic.delete', topicData); + plugins.fireHook('action:topic.delete', { topic: topicData, uid: uid }); } else { - plugins.fireHook('action:topic.restore', topicData); + plugins.fireHook('action:topic.restore', { topic: topicData, uid: uid }); } var data = { @@ -113,18 +113,18 @@ module.exports = function (Topics) { function toggleLock(tid, uid, lock, callback) { callback = callback || function () {}; - var cid; + var topicData; async.waterfall([ function (next) { - Topics.getTopicField(tid, 'cid', next); + Topics.getTopicFields(tid, ['tid', 'uid', 'cid'], next); }, - function (_cid, next) { - cid = _cid; - if (!cid) { + function (_topicData, next) { + topicData = _topicData; + if (!topicData || !topicData.cid) { return next(new Error('[[error:no-topic]]')); } - privileges.categories.isAdminOrMod(cid, uid, next); + privileges.categories.isAdminOrMod(topicData.cid, uid, next); }, function (isAdminOrMod, next) { if (!isAdminOrMod) { @@ -134,16 +134,11 @@ module.exports = function (Topics) { Topics.setTopicField(tid, 'locked', lock ? 1 : 0, next); }, function (next) { - var data = { - tid: tid, - isLocked: lock, - uid: uid, - cid: cid, - }; + topicData.isLocked = lock; - plugins.fireHook('action:topic.lock', data); + plugins.fireHook('action:topic.lock', { topic: _.clone(topicData), uid: uid }); - next(null, data); + next(null, topicData); }, ], callback); } @@ -166,7 +161,7 @@ module.exports = function (Topics) { if (!exists) { return callback(new Error('[[error:no-topic]]')); } - Topics.getTopicFields(tid, ['cid', 'lastposttime', 'postcount'], next); + Topics.getTopicFields(tid, ['uid', 'tid', 'cid', 'lastposttime', 'postcount'], next); }, function (_topicData, next) { topicData = _topicData; @@ -197,16 +192,11 @@ module.exports = function (Topics) { ], next); }, function (results, next) { - var data = { - tid: tid, - isPinned: pin, - uid: uid, - cid: topicData.cid, - }; + topicData.isPinned = pin; - plugins.fireHook('action:topic.pin', data); + plugins.fireHook('action:topic.pin', { topic: _.clone(topicData), uid: uid }); - next(null, data); + next(null, topicData); }, ], callback); } diff --git a/src/topics/unread.js b/src/topics/unread.js index e089fdaad9..ef2f35f22c 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -17,13 +17,12 @@ module.exports = function (Topics) { callback = filter; filter = ''; } - Topics.getUnreadTids(0, uid, filter, function (err, tids) { + Topics.getUnreadTids({ cid: 0, uid: uid, filter: filter }, function (err, tids) { callback(err, Array.isArray(tids) ? tids.length : 0); }); }; - - Topics.getUnreadTopics = function (cid, uid, start, stop, filter, callback) { + Topics.getUnreadTopics = function (params, callback) { var unreadTopics = { showSelect: true, nextStart: 0, @@ -32,7 +31,7 @@ module.exports = function (Topics) { async.waterfall([ function (next) { - Topics.getUnreadTids(cid, uid, filter, next); + Topics.getUnreadTids(params, next); }, function (tids, next) { unreadTopics.topicCount = tids.length; @@ -41,13 +40,13 @@ module.exports = function (Topics) { return next(null, []); } - if (stop === -1) { - tids = tids.slice(start); + if (params.stop === -1) { + tids = tids.slice(params.start); } else { - tids = tids.slice(start, stop + 1); + tids = tids.slice(params.start, params.stop + 1); } - Topics.getTopicsByTids(tids, uid, next); + Topics.getTopicsByTids(tids, params.uid, next); }, function (topicData, next) { if (!Array.isArray(topicData) || !topicData.length) { @@ -55,7 +54,7 @@ module.exports = function (Topics) { } unreadTopics.topics = topicData; - unreadTopics.nextStart = stop + 1; + unreadTopics.nextStart = params.stop + 1; next(null, unreadTopics); }, ], callback); @@ -66,25 +65,17 @@ module.exports = function (Topics) { return Date.now() - (cutoff * 86400000); }; - Topics.getUnreadTids = function (cid, uid, filter, callback) { - uid = parseInt(uid, 10); + Topics.getUnreadTids = function (params, callback) { + var uid = parseInt(params.uid, 10); if (uid === 0) { return callback(null, []); } - var cutoff = Topics.unreadCutoff(); - - var ignoredCids; + var cutoff = params.cutoff || Topics.unreadCutoff(); async.waterfall([ function (next) { async.parallel({ - ignoredCids: function (next) { - if (filter === 'watched') { - return next(null, []); - } - user.getIgnoredCategories(uid, next); - }, ignoredTids: function (next) { user.getIgnoredTids(uid, 0, -1, next); }, @@ -104,8 +95,6 @@ module.exports = function (Topics) { return callback(null, []); } - ignoredCids = results.ignoredCids; - var userRead = {}; results.userScores.forEach(function (userItem) { userRead[userItem.value] = userItem.score; @@ -120,7 +109,7 @@ module.exports = function (Topics) { if (results.ignoredTids.indexOf(recentTopic.value.toString()) !== -1) { return false; } - switch (filter) { + switch (params.filter) { case 'new': return !userRead[recentTopic.value]; default: @@ -132,7 +121,7 @@ module.exports = function (Topics) { return array.indexOf(tid) === index; }); - if (filter === 'watched') { + if (params.filter === 'watched') { Topics.filterWatchedTids(tids, uid, next); } else { next(null, tids); @@ -141,14 +130,14 @@ module.exports = function (Topics) { function (tids, next) { tids = tids.slice(0, 200); - filterTopics(uid, tids, cid, ignoredCids, filter, next); + filterTopics(uid, tids, params.cid, params.filter, next); }, ], callback); }; - function filterTopics(uid, tids, cid, ignoredCids, filter, callback) { - if (!Array.isArray(ignoredCids) || !tids.length) { + function filterTopics(uid, tids, cid, filter, callback) { + if (!tids.length) { return callback(null, tids); } @@ -167,13 +156,19 @@ module.exports = function (Topics) { } db.sortedSetScores('uid:' + uid + ':followed_tids', tids, next); }, + ignoredCids: function (next) { + if (filter === 'watched') { + return next(null, []); + } + user.getIgnoredCategories(uid, next); + }, }, next); }, function (results, next) { var topics = results.topics; tids = topics.filter(function (topic, index) { return topic && topic.cid && - (!!results.isTopicsFollowed[index] || ignoredCids.indexOf(topic.cid.toString()) === -1) && + (!!results.isTopicsFollowed[index] || results.ignoredCids.indexOf(topic.cid.toString()) === -1) && (!cid || parseInt(cid, 10) === parseInt(topic.cid, 10)); }).map(function (topic) { return topic.tid; @@ -187,16 +182,22 @@ module.exports = function (Topics) { callback = callback || function () {}; if (!uid || parseInt(uid, 10) === 0) { - return callback(); + return setImmediate(callback); } - Topics.getTotalUnread(uid, function (err, count) { - if (err) { - return callback(err); - } - require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', count); - callback(); - }); + async.waterfall([ + function (next) { + async.parallel({ + unreadTopicCount: async.apply(Topics.getTotalUnread, uid), + unreadNewTopicCount: async.apply(Topics.getTotalUnread, uid, 'new'), + unreadWatchedTopicCount: async.apply(Topics.getTotalUnread, uid, 'watched'), + }, next); + }, + function (results, next) { + require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', results); + setImmediate(next); + }, + ], callback); }; Topics.markAsUnreadForAll = function (tid, callback) { @@ -362,14 +363,16 @@ module.exports = function (Topics) { }; Topics.filterNewTids = function (tids, uid, callback) { - db.sortedSetScores('uid:' + uid + ':tids_read', tids, function (err, scores) { - if (err) { - return callback(err); - } - tids = tids.filter(function (tid, index) { - return tid && !scores[index]; - }); - callback(null, tids); - }); + async.waterfall([ + function (next) { + db.sortedSetScores('uid:' + uid + ':tids_read', tids, next); + }, + function (scores, next) { + tids = tids.filter(function (tid, index) { + return tid && !scores[index]; + }); + next(null, tids); + }, + ], callback); }; }; diff --git a/src/upgrade.js b/src/upgrade.js index 8cba76974d..22c7867eeb 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -1,503 +1,187 @@ 'use strict'; +var async = require('async'); +var path = require('path'); +var semver = require('semver'); +var readline = require('readline'); var db = require('./database'); -var async = require('async'); -var winston = require('winston'); +var file = require('../src/file'); + +/* + * Need to write an upgrade script for NodeBB? Cool. + * + * 1. Copy TEMPLATE to a file name of your choice. Try to be succinct. + * 2. Open up that file and change the user-friendly name (can be longer/more descriptive than the file name) + * and timestamp + * 3. Add your script under the "method" property + * 4. Append your filename to the array below for the next NodeBB version. + */ var Upgrade = {}; -var minSchemaDate = Date.UTC(2016, 8, 7); // This value gets updated every new MAJOR version -var schemaDate; -var thisSchemaDate; - -// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema -var latestSchema = Date.UTC(2017, 3, 16); +Upgrade.getAll = function (callback) { + async.waterfall([ + async.apply(file.walk, path.join(__dirname, './upgrades')), + function (files, next) { + // Sort the upgrade scripts based on version + var versionA; + var versionB; + setImmediate(next, null, files.filter(function (file) { + return path.basename(file) !== 'TEMPLATE'; + }).sort(function (a, b) { + versionA = path.dirname(a).split('/').pop(); + versionB = path.dirname(b).split('/').pop(); + + return semver.compare(versionA, versionB); + })); + }, + ], callback); +}; Upgrade.check = function (callback) { - db.get('schemaDate', function (err, value) { - if (err) { - return callback(err); - } - - if (!value) { - db.set('schemaDate', latestSchema, function (err) { + // Throw 'schema-out-of-date' if not all upgrade scripts have run + async.waterfall([ + async.apply(Upgrade.getAll), + function (files, next) { + db.getSortedSetRange('schemaLog', 0, -1, function (err, executed) { if (err) { return callback(err); } - callback(null); - }); - return; - } - - var schema_ok = parseInt(value, 10) >= latestSchema; - callback(!schema_ok ? new Error('schema-out-of-date') : null); - }); -}; - -Upgrade.update = function (schemaDate, callback) { - db.set('schemaDate', schemaDate, callback); -}; -Upgrade.upgrade = function (callback) { - var updatesMade = false; - - winston.info('Beginning database schema update'); - - async.series([ - function (next) { - // Prepare for upgrade & check to make sure the upgrade is possible - db.get('schemaDate', function (err, value) { - if (err) { - return next(err); - } - - if (!value) { - db.set('schemaDate', latestSchema, function () { - next(); - }); - schemaDate = latestSchema; - } else { - schemaDate = parseInt(value, 10); - } + var remainder = files.filter(function (name) { + return executed.indexOf(path.basename(name, '.js')) === -1; + }); - if (schemaDate >= minSchemaDate) { - next(); - } else { - next(new Error('upgrade-not-possible')); - } + next(remainder.length > 0 ? new Error('schema-out-of-date') : null); }); }, - function (next) { - thisSchemaDate = Date.UTC(2016, 8, 22); - - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.info('[2016/09/22] Setting category recent tids'); - - - db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) { - if (err) { - return next(err); - } - - async.eachSeries(cids, function (cid, next) { - db.getSortedSetRevRange('cid:' + cid + ':pids', 0, 0, function (err, pid) { - if (err || !pid) { - return next(err); - } - db.getObjectFields('post:' + pid, ['tid', 'timestamp'], function (err, postData) { - if (err || !postData || !postData.tid) { - return next(err); - } - db.sortedSetAdd('cid:' + cid + ':recent_tids', postData.timestamp, postData.tid, next); - }); - }); - }, function (err) { - if (err) { - return next(err); - } - - winston.info('[2016/09/22] Setting category recent tids - done'); - Upgrade.update(thisSchemaDate, next); - }); - }); - } else { - winston.info('[2016/09/22] Setting category recent tids - skipped!'); - next(); - } - }, - function (next) { - function upgradePosts(next) { - var batch = require('./batch'); - - batch.processSortedSet('posts:pid', function (ids, next) { - async.each(ids, function (id, next) { - console.log('processing pid ' + id); - async.waterfall([ - function (next) { - db.rename('pid:' + id + ':users_favourited', 'pid:' + id + ':users_bookmarked', next); - }, - function (next) { - db.getObjectField('post:' + id, 'reputation', next); - }, - function (reputation, next) { - if (parseInt(reputation, 10)) { - db.setObjectField('post:' + id, 'bookmarks', reputation, next); - } else { - next(); - } - }, - function (next) { - db.deleteObjectField('post:' + id, 'reputation', next); - }, - ], next); - }, next); - }, {}, next); - } - - function upgradeUsers(next) { - var batch = require('./batch'); - - batch.processSortedSet('users:joindate', function (ids, next) { - async.each(ids, function (id, next) { - console.log('processing uid ' + id); - db.rename('uid:' + id + ':favourites', 'uid:' + id + ':bookmarks', next); - }, next); - }, {}, next); - } + ], callback); +}; - thisSchemaDate = Date.UTC(2016, 9, 8); +Upgrade.run = function (callback) { + process.stdout.write('\nParsing upgrade scripts... '); + var queue = []; + var skipped = 0; + + async.parallel({ + // Retrieve list of upgrades that have already been run + completed: async.apply(db.getSortedSetRange, 'schemaLog', 0, -1), + // ... and those available to be run + available: Upgrade.getAll, + }, function (err, data) { + if (err) { + return callback(err); + } - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.info('[2016/10/8] favourite -> bookmark refactor'); - async.series([upgradePosts, upgradeUsers], function (err) { - if (err) { - return next(err); - } - winston.info('[2016/08/05] favourite- bookmark refactor done!'); - Upgrade.update(thisSchemaDate, next); - }); + queue = data.available.reduce(function (memo, cur) { + if (data.completed.indexOf(path.basename(cur, '.js')) === -1) { + memo.push(cur); } else { - winston.info('[2016/10/8] favourite -> bookmark refactor - skipped!'); - next(); + skipped += 1; } - }, - function (next) { - thisSchemaDate = Date.UTC(2016, 9, 14); - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.info('[2016/10/14] Creating sorted sets for post replies'); + return memo; + }, queue); - var posts = require('./posts'); - var batch = require('./batch'); - batch.processSortedSet('posts:pid', function (ids, next) { - posts.getPostsFields(ids, ['pid', 'toPid', 'timestamp'], function (err, data) { - if (err) { - return next(err); - } + Upgrade.process(queue, skipped, callback); + }); +}; - async.eachSeries(data, function (postData, next) { - if (!parseInt(postData.toPid, 10)) { - return next(null); - } - console.log('processing pid: ' + postData.pid + ' toPid: ' + postData.toPid); - async.parallel([ - async.apply(db.sortedSetAdd, 'pid:' + postData.toPid + ':replies', postData.timestamp, postData.pid), - async.apply(db.incrObjectField, 'post:' + postData.toPid, 'replies'), - ], next); - }, next); - }); - }, function (err) { - if (err) { - return next(err); - } +Upgrade.runSingle = function (query, callback) { + process.stdout.write('\nParsing upgrade scripts... '); - winston.info('[2016/10/14] Creating sorted sets for post replies - done'); - Upgrade.update(thisSchemaDate, next); - }); - } else { - winston.info('[2016/10/14] Creating sorted sets for post replies - skipped!'); - next(); - } + async.waterfall([ + async.apply(file.walk, path.join(__dirname, './upgrades')), + function (files, next) { + next(null, files.filter(function (file) { + return path.basename(file, '.js') === query; + })); }, - function (next) { - thisSchemaDate = Date.UTC(2016, 10, 22); - - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.info('[2016/11/22] Update global and user language keys'); - - var user = require('./user'); - var meta = require('./meta'); - var batch = require('./batch'); - var newLanguage; - var i = 0; - var j = 0; - async.parallel([ - function (next) { - meta.configs.get('defaultLang', function (err, defaultLang) { - if (err) { - return next(err); - } - - if (!defaultLang) { - return setImmediate(next); - } + ], function (err, files) { + if (err) { + return callback(err); + } - newLanguage = defaultLang.replace('_', '-').replace('@', '-x-'); - if (newLanguage !== defaultLang) { - meta.configs.set('defaultLang', newLanguage, next); - } else { - setImmediate(next); - } - }); - }, - function (next) { - batch.processSortedSet('users:joindate', function (ids, next) { - async.each(ids, function (uid, next) { - async.waterfall([ - async.apply(db.getObjectField, 'user:' + uid + ':settings', 'userLang'), - function (language, next) { - i += 1; - if (!language) { - return setImmediate(next); - } + Upgrade.process(files, 0, callback); + }); +}; - newLanguage = language.replace('_', '-').replace('@', '-x-'); - if (newLanguage !== language) { - j += 1; - user.setSetting(uid, 'userLang', newLanguage, next); - } else { - setImmediate(next); - } - }, - ], next); - }, next); - }, next); - }, - ], function (err) { - if (err) { - return next(err); - } +Upgrade.process = function (files, skipCount, callback) { + process.stdout.write('OK'.green + ' | '.reset + String(files.length).cyan + ' script(s) found'.cyan + (skipCount > 0 ? ', '.cyan + String(skipCount).cyan + ' skipped'.cyan : '') + '\n'.reset); - winston.info('[2016/11/22] Update global and user language keys - done (' + i + ' processed, ' + j + ' updated)'); - Upgrade.update(thisSchemaDate, next); - }); - } else { - winston.info('[2016/11/22] Update global and user language keys - skipped!'); - next(); - } - }, + async.waterfall([ function (next) { - thisSchemaDate = Date.UTC(2016, 10, 25); - - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.info('[2016/11/25] Creating sorted sets for pinned topcis'); - - var topics = require('./topics'); - var batch = require('./batch'); - batch.processSortedSet('topics:tid', function (ids, next) { - topics.getTopicsFields(ids, ['tid', 'cid', 'pinned', 'lastposttime'], function (err, data) { - if (err) { - return next(err); - } - - data = data.filter(function (topicData) { - return parseInt(topicData.pinned, 10) === 1; - }); - - async.eachSeries(data, function (topicData, next) { - console.log('processing tid: ' + topicData.tid); - - async.parallel([ - async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:pinned', Date.now(), topicData.tid), - async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids', topicData.tid), - async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:posts', topicData.tid), - ], next); - }, next); - }); - }, function (err) { - if (err) { - return next(err); - } - - winston.info('[2016/11/25] Creating sorted sets for pinned topics - done'); - Upgrade.update(thisSchemaDate, next); - }); - } else { - winston.info('[2016/11/25] Creating sorted sets for pinned topics - skipped!'); - next(); - } + async.parallel({ + schemaDate: async.apply(db.get, 'schemaDate'), + schemaLogCount: async.apply(db.sortedSetCard, 'schemaLog'), + }, next); }, - function (next) { - thisSchemaDate = Date.UTC(2017, 1, 25); - var schemaName = '[2017/2/25] Update global and user sound settings'; - - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.verbose(schemaName); - - var meta = require('./meta'); - var batch = require('./batch'); - - var map = { - 'notification.mp3': 'Default | Deedle-dum', - 'waterdrop-high.mp3': 'Default | Water drop (high)', - 'waterdrop-low.mp3': 'Default | Water drop (low)', + function (results, next) { + async.eachSeries(files, function (file, next) { + var scriptExport = require(file); + var date = new Date(scriptExport.timestamp); + var version = path.dirname(file).split('/').pop(); + var progress = { + current: 0, + total: 0, + incr: Upgrade.incrementProgress, + script: scriptExport, + date: date, }; - async.parallel([ - function (cb) { - var keys = ['chat-incoming', 'chat-outgoing', 'notification']; - - db.getObject('settings:sounds', function (err, settings) { - if (err || !settings) { - return cb(err); - } - - keys.forEach(function (key) { - if (settings[key] && settings[key].indexOf(' | ') === -1) { - settings[key] = map[settings[key]] || ''; - } - }); - - meta.configs.setMultiple(settings, cb); - }); - }, - function (cb) { - var keys = ['notificationSound', 'incomingChatSound', 'outgoingChatSound']; + process.stdout.write(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '...\n '); - batch.processSortedSet('users:joindate', function (ids, next) { - async.each(ids, function (uid, next) { - db.getObject('user:' + uid + ':settings', function (err, settings) { - if (err || !settings) { - return next(err); - } - var newSettings = {}; - keys.forEach(function (key) { - if (settings[key] && settings[key].indexOf(' | ') === -1) { - newSettings[key] = map[settings[key]] || ''; - } - }); + // For backwards compatibility, cross-reference with schemaDate (if found). If a script's date is older, skip it + if ((!results.schemaDate && !results.schemaLogCount) || (scriptExport.timestamp <= results.schemaDate && semver.lt(version, '1.5.0'))) { + process.stdout.write('skipped\n'.grey); + db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'), next); + return; + } - if (Object.keys(newSettings).length) { - db.setObject('user:' + uid + ':settings', newSettings, next); - } else { - setImmediate(next); - } - }); - }, next); - }, cb); - }, - ], function (err) { + // Do the upgrade... + scriptExport.method.bind({ + progress: progress, + })(function (err) { if (err) { + process.stdout.write('error\n'.red); return next(err); } - winston.info(schemaName + ' - done'); - Upgrade.update(thisSchemaDate, next); - }); - } else { - winston.info(schemaName + ' - skipped!'); - next(); - } - }, - function (next) { - thisSchemaDate = Date.UTC(2017, 1, 28); - var schemaName = '[2017/2/28] Update urls in config to `/assets`'; - - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.info(schemaName); - async.waterfall([ - function (cb) { - db.getObject('config', cb); - }, - function (config, cb) { - if (!config) { - return cb(); - } - - var keys = ['brand:favicon', 'brand:touchicon', 'og:image', 'brand:logo:url', 'defaultAvatar', 'profile:defaultCovers']; - keys.forEach(function (key) { - var oldValue = config[key]; - - if (!oldValue || typeof oldValue !== 'string') { - return; - } - - config[key] = oldValue.replace(/(?:\/assets)?\/(images|uploads)\//g, '/assets/$1/'); - }); + if (progress.total > 0) { + readline.clearLine(process.stdout, 0); + readline.cursorTo(process.stdout, 0); + process.stdout.write(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '... '); + } - db.setObject('config', config, cb); - }, - function (next) { - winston.info(schemaName + ' - done'); - Upgrade.update(thisSchemaDate, next); - }, - ], next); - } else { - winston.info(schemaName + ' - skipped!'); - next(); - } + process.stdout.write('OK\n'.green); + // Record success in schemaLog + db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'), next); + }); + }, next); }, function (next) { - thisSchemaDate = Date.UTC(2017, 3, 16); - var schemaName = '[2017/4/16] Delete sessions'; - - if (schemaDate < thisSchemaDate) { - updatesMade = true; - winston.info(schemaName); - - var configJSON = require('../config.json'); - var isRedisSessionStore = configJSON.hasOwnProperty('redis'); - - async.waterfall([ - function (next) { - if (isRedisSessionStore) { - var rdb = require('./database/redis'); - var client = rdb.connect(); - async.waterfall([ - function (next) { - client.keys('sess:*', next); - }, - function (sessionKeys, next) { - async.eachSeries(sessionKeys, function (key, next) { - client.del(key, next); - }, next); - }, - ], function (err) { - next(err); - }); - } else { - db.client.collection('sessions').deleteMany({}, {}, function (err) { - next(err); - }); - } - }, - function (next) { - winston.info(schemaName + ' - done'); - Upgrade.update(thisSchemaDate, next); - }, - ], next); - } else { - winston.info(schemaName + ' - skipped!'); - next(); - } + process.stdout.write('Upgrade complete!\n\n'.green); + setImmediate(next); }, - // Add new schema updates here - // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!! - ], function (err) { - if (!err) { - if (updatesMade) { - winston.info('[upgrade] Schema update complete!'); - } else { - winston.info('[upgrade] Schema already up to date!'); - } - } else { - switch (err.message) { - case 'upgrade-not-possible': - winston.error('[upgrade] NodeBB upgrade could not complete, as your database schema is too far out of date.'); - winston.error('[upgrade] Please ensure that you did not skip any minor version upgrades.'); - winston.error('[upgrade] (e.g. v0.1.x directly to v0.3.x)'); - break; - - default: - winston.error('[upgrade] Errors were encountered while updating the NodeBB schema: ' + err.message); - break; - } - } + ], callback); +}; - if (typeof callback === 'function') { - callback(err); - } else { - process.exit(); - } - }); +Upgrade.incrementProgress = function () { + this.current += 1; + + // Redraw the progress bar + var percentage = 0; + var filled = 0; + var unfilled = 15; + if (this.total) { + percentage = Math.floor((this.current / this.total) * 100) + '%'; + filled = Math.floor((this.current / this.total) * 15); + unfilled = 15 - filled; + } + + readline.cursorTo(process.stdout, 0); + process.stdout.write(' [' + (filled ? new Array(filled).join('#') : '') + new Array(unfilled).join(' ') + '] (' + this.current + '/' + (this.total || '??') + ') ' + percentage + ' '); }; module.exports = Upgrade; diff --git a/src/upgrades/1.0.0/chat_room_hashes.js b/src/upgrades/1.0.0/chat_room_hashes.js new file mode 100644 index 0000000000..ae52be31f7 --- /dev/null +++ b/src/upgrades/1.0.0/chat_room_hashes.js @@ -0,0 +1,39 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Chat room hashes', + timestamp: Date.UTC(2015, 11, 23), + method: function (callback) { + db.getObjectField('global', 'nextChatRoomId', function (err, nextChatRoomId) { + if (err) { + return callback(err); + } + var currentChatRoomId = 1; + async.whilst(function () { + return currentChatRoomId <= nextChatRoomId; + }, function (next) { + db.getSortedSetRange('chat:room:' + currentChatRoomId + ':uids', 0, 0, function (err, uids) { + if (err) { + return next(err); + } + if (!Array.isArray(uids) || !uids.length || !uids[0]) { + currentChatRoomId += 1; + return next(); + } + + db.setObject('chat:room:' + currentChatRoomId, { owner: uids[0], roomId: currentChatRoomId }, function (err) { + if (err) { + return next(err); + } + currentChatRoomId += 1; + next(); + }); + }); + }, callback); + }); + }, +}; diff --git a/src/upgrades/1.0.0/chat_upgrade.js b/src/upgrades/1.0.0/chat_upgrade.js new file mode 100644 index 0000000000..d5a971d4b4 --- /dev/null +++ b/src/upgrades/1.0.0/chat_upgrade.js @@ -0,0 +1,85 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Upgrading chats', + timestamp: Date.UTC(2015, 11, 15), + method: function (callback) { + db.getObjectFields('global', ['nextMid', 'nextChatRoomId'], function (err, globalData) { + if (err) { + return callback(err); + } + + var rooms = {}; + var roomId = globalData.nextChatRoomId || 1; + var currentMid = 1; + + async.whilst(function () { + return currentMid <= globalData.nextMid; + }, function (next) { + db.getObject('message:' + currentMid, function (err, message) { + var msgTime; + + function addMessageToUids(roomId, callback) { + async.parallel([ + function (next) { + db.sortedSetAdd('uid:' + message.fromuid + ':chat:room:' + roomId + ':mids', msgTime, currentMid, next); + }, + function (next) { + db.sortedSetAdd('uid:' + message.touid + ':chat:room:' + roomId + ':mids', msgTime, currentMid, next); + }, + ], callback); + } + + if (err || !message) { + winston.verbose('skipping chat message ', currentMid); + currentMid += 1; + return next(err); + } + + var pairID = [parseInt(message.fromuid, 10), parseInt(message.touid, 10)].sort().join(':'); + msgTime = parseInt(message.timestamp, 10); + + if (rooms[pairID]) { + winston.verbose('adding message ' + currentMid + ' to existing roomID ' + roomId); + addMessageToUids(rooms[pairID], function (err) { + if (err) { + return next(err); + } + currentMid += 1; + next(); + }); + } else { + winston.verbose('adding message ' + currentMid + ' to new roomID ' + roomId); + async.parallel([ + function (next) { + db.sortedSetAdd('uid:' + message.fromuid + ':chat:rooms', msgTime, roomId, next); + }, + function (next) { + db.sortedSetAdd('uid:' + message.touid + ':chat:rooms', msgTime, roomId, next); + }, + function (next) { + db.sortedSetAdd('chat:room:' + roomId + ':uids', [msgTime, msgTime + 1], [message.fromuid, message.touid], next); + }, + function (next) { + addMessageToUids(roomId, next); + }, + ], function (err) { + if (err) { + return next(err); + } + rooms[pairID] = roomId; + roomId += 1; + currentMid += 1; + db.setObjectField('global', 'nextChatRoomId', roomId, next); + }); + } + }); + }, callback); + }); + }, +}; diff --git a/src/upgrades/1.0.0/global_moderators.js b/src/upgrades/1.0.0/global_moderators.js new file mode 100644 index 0000000000..7639da9453 --- /dev/null +++ b/src/upgrades/1.0.0/global_moderators.js @@ -0,0 +1,32 @@ +'use strict'; + +var async = require('async'); + +module.exports = { + name: 'Creating Global moderators group', + timestamp: Date.UTC(2016, 0, 23), + method: function (callback) { + var groups = require('../../groups'); + async.waterfall([ + function (next) { + groups.exists('Global Moderators', next); + }, + function (exists, next) { + if (exists) { + return next(null, null); + } + groups.create({ + name: 'Global Moderators', + userTitle: 'Global Moderator', + description: 'Forum wide moderators', + hidden: 0, + private: 1, + disableJoinRequests: 1, + }, next); + }, + function (groupData, next) { + groups.show('Global Moderators', next); + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.0.0/social_post_sharing.js b/src/upgrades/1.0.0/social_post_sharing.js new file mode 100644 index 0000000000..3477da86bb --- /dev/null +++ b/src/upgrades/1.0.0/social_post_sharing.js @@ -0,0 +1,21 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Social: Post Sharing', + timestamp: Date.UTC(2016, 1, 25), + method: function (callback) { + var social = require('../../social'); + async.parallel([ + function (next) { + social.setActivePostSharingNetworks(['facebook', 'google', 'twitter'], next); + }, + function (next) { + db.deleteObjectField('config', 'disableSocialButtons', next); + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.0.0/theme_to_active_plugins.js b/src/upgrades/1.0.0/theme_to_active_plugins.js new file mode 100644 index 0000000000..07b95f45aa --- /dev/null +++ b/src/upgrades/1.0.0/theme_to_active_plugins.js @@ -0,0 +1,16 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Adding theme to active plugins sorted set', + timestamp: Date.UTC(2015, 11, 23), + method: function (callback) { + async.waterfall([ + async.apply(db.getObjectField, 'config', 'theme:id'), + async.apply(db.sortedSetAdd, 'plugins:active', 0), + ], callback); + }, +}; diff --git a/src/upgrades/1.0.0/user_best_posts.js b/src/upgrades/1.0.0/user_best_posts.js new file mode 100644 index 0000000000..986e88efa7 --- /dev/null +++ b/src/upgrades/1.0.0/user_best_posts.js @@ -0,0 +1,33 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Creating user best post sorted sets', + timestamp: Date.UTC(2016, 0, 14), + method: function (callback) { + var batch = require('../../batch'); + var progress = this.progress; + + batch.processSortedSet('posts:pid', function (ids, next) { + async.eachSeries(ids, function (id, next) { + db.getObjectFields('post:' + id, ['pid', 'uid', 'votes'], function (err, postData) { + if (err) { + return next(err); + } + if (!postData || !parseInt(postData.votes, 10) || !parseInt(postData.uid, 10)) { + return next(); + } + winston.verbose('processing pid: ' + postData.pid + ' uid: ' + postData.uid + ' votes: ' + postData.votes); + db.sortedSetAdd('uid:' + postData.uid + ':posts:votes', postData.votes, postData.pid, next); + progress.incr(); + }); + }, next); + }, { + progress: progress, + }, callback); + }, +}; diff --git a/src/upgrades/1.0.0/users_notvalidated.js b/src/upgrades/1.0.0/users_notvalidated.js new file mode 100644 index 0000000000..a69deb90df --- /dev/null +++ b/src/upgrades/1.0.0/users_notvalidated.js @@ -0,0 +1,29 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Creating users:notvalidated', + timestamp: Date.UTC(2016, 0, 20), + method: function (callback) { + var batch = require('../../batch'); + var now = Date.now(); + batch.processSortedSet('users:joindate', function (ids, next) { + async.eachSeries(ids, function (id, next) { + db.getObjectFields('user:' + id, ['uid', 'email:confirmed'], function (err, userData) { + if (err) { + return next(err); + } + if (!userData || !parseInt(userData.uid, 10) || parseInt(userData['email:confirmed'], 10) === 1) { + return next(); + } + winston.verbose('processing uid: ' + userData.uid + ' email:confirmed: ' + userData['email:confirmed']); + db.sortedSetAdd('users:notvalidated', now, userData.uid, next); + }); + }, next); + }, callback); + }, +}; diff --git a/src/upgrades/1.1.0/assign_topic_read_privilege.js b/src/upgrades/1.1.0/assign_topic_read_privilege.js new file mode 100644 index 0000000000..93d06485e6 --- /dev/null +++ b/src/upgrades/1.1.0/assign_topic_read_privilege.js @@ -0,0 +1,71 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Giving topics:read privs to any group that was previously allowed to Find & Access Category', + timestamp: Date.UTC(2016, 4, 28), + method: function (callback) { + var groupsAPI = require('../../groups'); + var privilegesAPI = require('../../privileges'); + + db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) { + if (err) { + return callback(err); + } + + async.eachSeries(cids, function (cid, next) { + privilegesAPI.categories.list(cid, function (err, data) { + if (err) { + return next(err); + } + + var groups = data.groups; + var users = data.users; + + async.waterfall([ + function (next) { + async.eachSeries(groups, function (group, next) { + if (group.privileges['groups:read']) { + return groupsAPI.join('cid:' + cid + ':privileges:groups:topics:read', group.name, function (err) { + if (!err) { + winston.verbose('cid:' + cid + ':privileges:groups:topics:read granted to gid: ' + group.name); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function (next) { + async.eachSeries(users, function (user, next) { + if (user.privileges.read) { + return groupsAPI.join('cid:' + cid + ':privileges:topics:read', user.uid, function (err) { + if (!err) { + winston.verbose('cid:' + cid + ':privileges:topics:read granted to uid: ' + user.uid); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + ], function (err) { + if (!err) { + winston.verbose('-- cid ' + cid + ' upgraded'); + } + + next(err); + }); + }); + }, callback); + }); + }, +}; diff --git a/src/upgrades/1.1.0/dismiss_flags_from_deleted_topics.js b/src/upgrades/1.1.0/dismiss_flags_from_deleted_topics.js new file mode 100644 index 0000000000..6cbf6b7254 --- /dev/null +++ b/src/upgrades/1.1.0/dismiss_flags_from_deleted_topics.js @@ -0,0 +1,41 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Dismiss flags from deleted topics', + timestamp: Date.UTC(2016, 3, 29), + method: function (callback) { + var posts = require('../../posts'); + var topics = require('../../topics'); + + var pids; + var tids; + + async.waterfall([ + async.apply(db.getSortedSetRange, 'posts:flagged', 0, -1), + function (_pids, next) { + pids = _pids; + posts.getPostsFields(pids, ['tid'], next); + }, + function (_tids, next) { + tids = _tids.map(function (a) { + return a.tid; + }); + + topics.getTopicsFields(tids, ['deleted'], next); + }, + function (state, next) { + var toDismiss = state.map(function (a, idx) { + return parseInt(a.deleted, 10) === 1 ? pids[idx] : null; + }).filter(Boolean); + + winston.verbose('[2016/04/29] ' + toDismiss.length + ' dismissable flags found'); + async.each(toDismiss, posts.dismissFlag, next); + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.1.0/group_title_update.js b/src/upgrades/1.1.0/group_title_update.js new file mode 100644 index 0000000000..7b8847f276 --- /dev/null +++ b/src/upgrades/1.1.0/group_title_update.js @@ -0,0 +1,32 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Group title from settings to user profile', + timestamp: Date.UTC(2016, 3, 14), + method: function (callback) { + var user = require('../../user'); + var batch = require('../../batch'); + var count = 0; + batch.processSortedSet('users:joindate', function (uids, next) { + winston.verbose('upgraded ' + count + ' users'); + user.getMultipleUserSettings(uids, function (err, settings) { + if (err) { + return next(err); + } + count += uids.length; + settings = settings.filter(function (setting) { + return setting && setting.groupTitle; + }); + + async.each(settings, function (setting, next) { + db.setObjectField('user:' + setting.uid, 'groupTitle', setting.groupTitle, next); + }, next); + }); + }, {}, callback); + }, +}; diff --git a/src/upgrades/1.1.0/separate_upvote_downvote.js b/src/upgrades/1.1.0/separate_upvote_downvote.js new file mode 100644 index 0000000000..dbfb5aee09 --- /dev/null +++ b/src/upgrades/1.1.0/separate_upvote_downvote.js @@ -0,0 +1,54 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Store upvotes/downvotes separately', + timestamp: Date.UTC(2016, 5, 13), + method: function (callback) { + var batch = require('../../batch'); + var posts = require('../../posts'); + var count = 0; + var progress = this.progress; + + batch.processSortedSet('posts:pid', function (pids, next) { + winston.verbose('upgraded ' + count + ' posts'); + count += pids.length; + async.each(pids, function (pid, next) { + async.parallel({ + upvotes: function (next) { + db.setCount('pid:' + pid + ':upvote', next); + }, + downvotes: function (next) { + db.setCount('pid:' + pid + ':downvote', next); + }, + }, function (err, results) { + if (err) { + return next(err); + } + var data = {}; + + if (parseInt(results.upvotes, 10) > 0) { + data.upvotes = results.upvotes; + } + if (parseInt(results.downvotes, 10) > 0) { + data.downvotes = results.downvotes; + } + + if (Object.keys(data).length) { + posts.setPostFields(pid, data, next); + } else { + next(); + } + + progress.incr(); + }, next); + }, next); + }, { + progress: progress, + }, callback); + }, +}; diff --git a/src/upgrades/1.1.0/user_post_count_per_tid.js b/src/upgrades/1.1.0/user_post_count_per_tid.js new file mode 100644 index 0000000000..f275e7a9a4 --- /dev/null +++ b/src/upgrades/1.1.0/user_post_count_per_tid.js @@ -0,0 +1,48 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Users post count per tid', + timestamp: Date.UTC(2016, 3, 19), + method: function (callback) { + var batch = require('../../batch'); + var topics = require('../../topics'); + var count = 0; + batch.processSortedSet('topics:tid', function (tids, next) { + winston.verbose('upgraded ' + count + ' topics'); + count += tids.length; + async.each(tids, function (tid, next) { + db.delete('tid:' + tid + ':posters', function (err) { + if (err) { + return next(err); + } + topics.getPids(tid, function (err, pids) { + if (err) { + return next(err); + } + + if (!pids.length) { + return next(); + } + + async.eachSeries(pids, function (pid, next) { + db.getObjectField('post:' + pid, 'uid', function (err, uid) { + if (err) { + return next(err); + } + if (!parseInt(uid, 10)) { + return next(); + } + db.sortedSetIncrBy('tid:' + tid + ':posters', 1, uid, next); + }); + }, next); + }); + }); + }, next); + }, {}, callback); + }, +}; diff --git a/src/upgrades/1.1.1/remove_negative_best_posts.js b/src/upgrades/1.1.1/remove_negative_best_posts.js new file mode 100644 index 0000000000..cadf1a397f --- /dev/null +++ b/src/upgrades/1.1.1/remove_negative_best_posts.js @@ -0,0 +1,20 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Removing best posts with negative scores', + timestamp: Date.UTC(2016, 7, 5), + method: function (callback) { + var batch = require('../../batch'); + batch.processSortedSet('users:joindate', function (ids, next) { + async.each(ids, function (id, next) { + winston.verbose('processing uid ' + id); + db.sortedSetsRemoveRangeByScore(['uid:' + id + ':posts:votes'], '-inf', 0, next); + }, next); + }, {}, callback); + }, +}; diff --git a/src/upgrades/1.1.1/upload_privileges.js b/src/upgrades/1.1.1/upload_privileges.js new file mode 100644 index 0000000000..217e6e19fe --- /dev/null +++ b/src/upgrades/1.1.1/upload_privileges.js @@ -0,0 +1,38 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Giving upload privileges', + timestamp: Date.UTC(2016, 6, 12), + method: function (callback) { + var privilegesAPI = require('../../privileges'); + var meta = require('../../meta'); + + db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) { + if (err) { + return callback(err); + } + + async.eachSeries(cids, function (cid, next) { + privilegesAPI.categories.list(cid, function (err, data) { + if (err) { + return next(err); + } + async.eachSeries(data.groups, function (group, next) { + if (group.name === 'guests' && parseInt(meta.config.allowGuestUploads, 10) !== 1) { + return next(); + } + if (group.privileges['groups:read']) { + privilegesAPI.categories.give(['upload:post:image'], cid, group.name, next); + } else { + next(); + } + }, next); + }); + }, callback); + }); + }, +}; diff --git a/src/upgrades/1.2.0/category_recent_tids.js b/src/upgrades/1.2.0/category_recent_tids.js new file mode 100644 index 0000000000..3d8d1cdf9c --- /dev/null +++ b/src/upgrades/1.2.0/category_recent_tids.js @@ -0,0 +1,31 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Category recent tids', + timestamp: Date.UTC(2016, 8, 22), + method: function (callback) { + db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) { + if (err) { + return callback(err); + } + + async.eachSeries(cids, function (cid, next) { + db.getSortedSetRevRange('cid:' + cid + ':pids', 0, 0, function (err, pid) { + if (err || !pid) { + return next(err); + } + db.getObjectFields('post:' + pid, ['tid', 'timestamp'], function (err, postData) { + if (err || !postData || !postData.tid) { + return next(err); + } + db.sortedSetAdd('cid:' + cid + ':recent_tids', postData.timestamp, postData.tid, next); + }); + }); + }, callback); + }); + }, +}; diff --git a/src/upgrades/1.2.0/edit_delete_deletetopic_privileges.js b/src/upgrades/1.2.0/edit_delete_deletetopic_privileges.js new file mode 100644 index 0000000000..40f8eda0ce --- /dev/null +++ b/src/upgrades/1.2.0/edit_delete_deletetopic_privileges.js @@ -0,0 +1,107 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Granting edit/delete/delete topic on existing categories', + timestamp: Date.UTC(2016, 7, 7), + method: function (callback) { + var groupsAPI = require('../../groups'); + var privilegesAPI = require('../../privileges'); + + db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) { + if (err) { + return callback(err); + } + + async.eachSeries(cids, function (cid, next) { + privilegesAPI.categories.list(cid, function (err, data) { + if (err) { + return next(err); + } + + var groups = data.groups; + var users = data.users; + + async.waterfall([ + function (next) { + async.eachSeries(groups, function (group, next) { + if (group.privileges['groups:topics:reply']) { + return async.parallel([ + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:posts:edit', group.name), + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:posts:delete', group.name), + ], function (err) { + if (!err) { + winston.verbose('cid:' + cid + ':privileges:groups:posts:edit, cid:' + cid + ':privileges:groups:posts:delete granted to gid: ' + group.name); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function (next) { + async.eachSeries(groups, function (group, next) { + if (group.privileges['groups:topics:create']) { + return groupsAPI.join('cid:' + cid + ':privileges:groups:topics:delete', group.name, function (err) { + if (!err) { + winston.verbose('cid:' + cid + ':privileges:groups:topics:delete granted to gid: ' + group.name); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function (next) { + async.eachSeries(users, function (user, next) { + if (user.privileges['topics:reply']) { + return async.parallel([ + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:posts:edit', user.uid), + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:posts:delete', user.uid), + ], function (err) { + if (!err) { + winston.verbose('cid:' + cid + ':privileges:posts:edit, cid:' + cid + ':privileges:posts:delete granted to uid: ' + user.uid); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function (next) { + async.eachSeries(users, function (user, next) { + if (user.privileges['topics:create']) { + return groupsAPI.join('cid:' + cid + ':privileges:topics:delete', user.uid, function (err) { + if (!err) { + winston.verbose('cid:' + cid + ':privileges:topics:delete granted to uid: ' + user.uid); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + ], function (err) { + if (!err) { + winston.verbose('-- cid ' + cid + ' upgraded'); + } + + next(err); + }); + }); + }, callback); + }); + }, +}; diff --git a/src/upgrades/1.3.0/favourites_to_bookmarks.js b/src/upgrades/1.3.0/favourites_to_bookmarks.js new file mode 100644 index 0000000000..b1037c860f --- /dev/null +++ b/src/upgrades/1.3.0/favourites_to_bookmarks.js @@ -0,0 +1,56 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Favourites to Bookmarks', + timestamp: Date.UTC(2016, 9, 8), + method: function (callback) { + var progress = this.progress; + + function upgradePosts(next) { + var batch = require('../../batch'); + + batch.processSortedSet('posts:pid', function (ids, next) { + async.each(ids, function (id, next) { + progress.incr(); + + async.waterfall([ + function (next) { + db.rename('pid:' + id + ':users_favourited', 'pid:' + id + ':users_bookmarked', next); + }, + function (next) { + db.getObjectField('post:' + id, 'reputation', next); + }, + function (reputation, next) { + if (parseInt(reputation, 10)) { + db.setObjectField('post:' + id, 'bookmarks', reputation, next); + } else { + next(); + } + }, + function (next) { + db.deleteObjectField('post:' + id, 'reputation', next); + }, + ], next); + }, next); + }, { + progress: progress, + }, next); + } + + function upgradeUsers(next) { + var batch = require('../../batch'); + + batch.processSortedSet('users:joindate', function (ids, next) { + async.each(ids, function (id, next) { + db.rename('uid:' + id + ':favourites', 'uid:' + id + ':bookmarks', next); + }, next); + }, {}, next); + } + + async.series([upgradePosts, upgradeUsers], callback); + }, +}; diff --git a/src/upgrades/1.3.0/sorted_sets_for_post_replies.js b/src/upgrades/1.3.0/sorted_sets_for_post_replies.js new file mode 100644 index 0000000000..e5a5506591 --- /dev/null +++ b/src/upgrades/1.3.0/sorted_sets_for_post_replies.js @@ -0,0 +1,39 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Sorted sets for post replies', + timestamp: Date.UTC(2016, 9, 14), + method: function (callback) { + var posts = require('../../posts'); + var batch = require('../../batch'); + var progress = this.progress; + + batch.processSortedSet('posts:pid', function (ids, next) { + posts.getPostsFields(ids, ['pid', 'toPid', 'timestamp'], function (err, data) { + if (err) { + return next(err); + } + + progress.incr(); + + async.eachSeries(data, function (postData, next) { + if (!parseInt(postData.toPid, 10)) { + return next(null); + } + winston.verbose('processing pid: ' + postData.pid + ' toPid: ' + postData.toPid); + async.parallel([ + async.apply(db.sortedSetAdd, 'pid:' + postData.toPid + ':replies', postData.timestamp, postData.pid), + async.apply(db.incrObjectField, 'post:' + postData.toPid, 'replies'), + ], next); + }, next); + }); + }, { + progress: progress, + }, callback); + }, +}; diff --git a/src/upgrades/1.4.0/global_and_user_language_keys.js b/src/upgrades/1.4.0/global_and_user_language_keys.js new file mode 100644 index 0000000000..3428ad5678 --- /dev/null +++ b/src/upgrades/1.4.0/global_and_user_language_keys.js @@ -0,0 +1,57 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Update global and user language keys', + timestamp: Date.UTC(2016, 10, 22), + method: function (callback) { + var user = require('../../user'); + var meta = require('../../meta'); + var batch = require('../../batch'); + var newLanguage; + async.parallel([ + function (next) { + meta.configs.get('defaultLang', function (err, defaultLang) { + if (err) { + return next(err); + } + + if (!defaultLang) { + return setImmediate(next); + } + + newLanguage = defaultLang.replace('_', '-').replace('@', '-x-'); + if (newLanguage !== defaultLang) { + meta.configs.set('defaultLang', newLanguage, next); + } else { + setImmediate(next); + } + }); + }, + function (next) { + batch.processSortedSet('users:joindate', function (ids, next) { + async.each(ids, function (uid, next) { + async.waterfall([ + async.apply(db.getObjectField, 'user:' + uid + ':settings', 'userLang'), + function (language, next) { + if (!language) { + return setImmediate(next); + } + + newLanguage = language.replace('_', '-').replace('@', '-x-'); + if (newLanguage !== language) { + user.setSetting(uid, 'userLang', newLanguage, next); + } else { + setImmediate(next); + } + }, + ], next); + }, next); + }, next); + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.4.0/sorted_set_for_pinned_topics.js b/src/upgrades/1.4.0/sorted_set_for_pinned_topics.js new file mode 100644 index 0000000000..c461c8df05 --- /dev/null +++ b/src/upgrades/1.4.0/sorted_set_for_pinned_topics.js @@ -0,0 +1,36 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'Sorted set for pinned topics', + timestamp: Date.UTC(2016, 10, 25), + method: function (callback) { + var topics = require('../../topics'); + var batch = require('../../batch'); + batch.processSortedSet('topics:tid', function (ids, next) { + topics.getTopicsFields(ids, ['tid', 'cid', 'pinned', 'lastposttime'], function (err, data) { + if (err) { + return next(err); + } + + data = data.filter(function (topicData) { + return parseInt(topicData.pinned, 10) === 1; + }); + + async.eachSeries(data, function (topicData, next) { + winston.verbose('processing tid: ' + topicData.tid); + + async.parallel([ + async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:pinned', Date.now(), topicData.tid), + async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids', topicData.tid), + async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:posts', topicData.tid), + ], next); + }, next); + }); + }, callback); + }, +}; diff --git a/src/upgrades/1.4.4/config_urls_update.js b/src/upgrades/1.4.4/config_urls_update.js new file mode 100644 index 0000000000..02689ceecb --- /dev/null +++ b/src/upgrades/1.4.4/config_urls_update.js @@ -0,0 +1,36 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Upgrading config urls to use assets route', + timestamp: Date.UTC(2017, 1, 28), + method: function (callback) { + async.waterfall([ + function (cb) { + db.getObject('config', cb); + }, + function (config, cb) { + if (!config) { + return cb(); + } + + var keys = ['brand:favicon', 'brand:touchicon', 'og:image', 'brand:logo:url', 'defaultAvatar', 'profile:defaultCovers']; + + keys.forEach(function (key) { + var oldValue = config[key]; + + if (!oldValue || typeof oldValue !== 'string') { + return; + } + + config[key] = oldValue.replace(/(?:\/assets)?\/(images|uploads)\//g, '/assets/$1/'); + }); + + db.setObject('config', config, cb); + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.4.4/sound_settings.js b/src/upgrades/1.4.4/sound_settings.js new file mode 100644 index 0000000000..57768fcc65 --- /dev/null +++ b/src/upgrades/1.4.4/sound_settings.js @@ -0,0 +1,65 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Update global and user sound settings', + timestamp: Date.UTC(2017, 1, 25), + method: function (callback) { + var meta = require('../../meta'); + var batch = require('../../batch'); + + var map = { + 'notification.mp3': 'Default | Deedle-dum', + 'waterdrop-high.mp3': 'Default | Water drop (high)', + 'waterdrop-low.mp3': 'Default | Water drop (low)', + }; + + async.parallel([ + function (cb) { + var keys = ['chat-incoming', 'chat-outgoing', 'notification']; + + db.getObject('settings:sounds', function (err, settings) { + if (err || !settings) { + return cb(err); + } + + keys.forEach(function (key) { + if (settings[key] && settings[key].indexOf(' | ') === -1) { + settings[key] = map[settings[key]] || ''; + } + }); + + meta.configs.setMultiple(settings, cb); + }); + }, + function (cb) { + var keys = ['notificationSound', 'incomingChatSound', 'outgoingChatSound']; + + batch.processSortedSet('users:joindate', function (ids, next) { + async.each(ids, function (uid, next) { + db.getObject('user:' + uid + ':settings', function (err, settings) { + if (err || !settings) { + return next(err); + } + var newSettings = {}; + keys.forEach(function (key) { + if (settings[key] && settings[key].indexOf(' | ') === -1) { + newSettings[key] = map[settings[key]] || ''; + } + }); + + if (Object.keys(newSettings).length) { + db.setObject('user:' + uid + ':settings', newSettings, next); + } else { + setImmediate(next); + } + }); + }, next); + }, cb); + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.4.6/delete_sessions.js b/src/upgrades/1.4.6/delete_sessions.js new file mode 100644 index 0000000000..571d4e125c --- /dev/null +++ b/src/upgrades/1.4.6/delete_sessions.js @@ -0,0 +1,49 @@ +'use strict'; + +var db = require('../../database'); +var async = require('async'); + +module.exports = { + name: 'Delete accidentally long-lived sessions', + timestamp: Date.UTC(2017, 3, 16), + method: function (callback) { + var configJSON = require.main.require('./config.json'); + var isRedisSessionStore = configJSON.hasOwnProperty('redis'); + var progress = this.progress; + + async.waterfall([ + function (next) { + if (isRedisSessionStore) { + var rdb = require.main.require('./src/database/redis'); + var batch = require.main.require('./src/batch'); + var client = rdb.connect(); + async.waterfall([ + function (next) { + client.keys('sess:*', next); + }, + function (sessionKeys, next) { + progress.total = sessionKeys.length; + + batch.processArray(sessionKeys, function (keys, next) { + var multi = client.multi(); + keys.forEach(function (key) { + progress.incr(); + multi.del(key); + }); + multi.exec(next); + }, { + batch: 1000, + }, next); + }, + ], function (err) { + next(err); + }); + } else { + db.client.collection('sessions').deleteMany({}, {}, function (err) { + next(err); + }); + } + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.5.0/allowed_file_extensions.js b/src/upgrades/1.5.0/allowed_file_extensions.js new file mode 100644 index 0000000000..29e348f16f --- /dev/null +++ b/src/upgrades/1.5.0/allowed_file_extensions.js @@ -0,0 +1,16 @@ +'use strict'; + +var db = require('../../database'); + +module.exports = { + name: 'Set default allowed file extensions', + timestamp: Date.UTC(2017, 3, 14), + method: function (callback) { + db.getObjectField('config', 'allowedFileExtensions', function (err, value) { + if (err || value) { + return callback(err); + } + db.setObjectField('config', 'allowedFileExtensions', 'png,jpg,bmp', callback); + }); + }, +}; diff --git a/src/upgrades/1.5.0/flags_refactor.js b/src/upgrades/1.5.0/flags_refactor.js new file mode 100644 index 0000000000..f80714ee50 --- /dev/null +++ b/src/upgrades/1.5.0/flags_refactor.js @@ -0,0 +1,92 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'Migrating flags to new schema', + timestamp: Date.UTC(2016, 11, 7), + method: function (callback) { + var batch = require('../../batch'); + var posts = require('../../posts'); + var flags = require('../../flags'); + var progress = this.progress; + + batch.processSortedSet('posts:pid', function (ids, next) { + posts.getPostsByPids(ids, 1, function (err, posts) { + if (err) { + return next(err); + } + + posts = posts.filter(function (post) { + return post.hasOwnProperty('flags'); + }); + + async.each(posts, function (post, next) { + progress.incr(); + + async.parallel({ + uids: async.apply(db.getSortedSetRangeWithScores, 'pid:' + post.pid + ':flag:uids', 0, -1), + reasons: async.apply(db.getSortedSetRange, 'pid:' + post.pid + ':flag:uid:reason', 0, -1), + }, function (err, data) { + if (err) { + return next(err); + } + + // Adding in another check here in case a post was improperly dismissed (flag count > 1 but no flags in db) + if (!data.uids.length || !data.reasons.length) { + return setImmediate(next); + } + + // Just take the first entry + var datetime = data.uids[0].score; + var reason = data.reasons[0].split(':')[1]; + var flagObj; + + async.waterfall([ + async.apply(flags.create, 'post', post.pid, data.uids[0].value, reason, datetime), + function (_flagObj, next) { + flagObj = _flagObj; + if (post['flag:state'] || post['flag:assignee']) { + flags.update(flagObj.flagId, 1, { + state: post['flag:state'], + assignee: post['flag:assignee'], + datetime: datetime, + }, next); + } else { + setImmediate(next); + } + }, + function (next) { + if (post.hasOwnProperty('flag:notes') && post['flag:notes'].length) { + try { + var history = JSON.parse(post['flag:history']); + history = history.filter(function (event) { + return event.type === 'notes'; + })[0]; + + flags.appendNote(flagObj.flagId, history.uid, post['flag:notes'], history.timestamp, next); + } catch (e) { + next(e); + } + } else { + setImmediate(next); + } + }, + ], function (err) { + if (err && err.message === '[[error:already-flagged]]') { + // Already flagged, no need to parse, but not an error + next(); + } else { + next(err); + } + }); + }); + }, next); + }); + }, { + progress: this.progress, + }, callback); + }, +}; diff --git a/src/upgrades/1.5.0/moderation_history_refactor.js b/src/upgrades/1.5.0/moderation_history_refactor.js new file mode 100644 index 0000000000..59e10367d8 --- /dev/null +++ b/src/upgrades/1.5.0/moderation_history_refactor.js @@ -0,0 +1,35 @@ +'use strict'; + +var db = require('../../database'); +var batch = require('../../batch'); + +var async = require('async'); + +module.exports = { + name: 'Update moderation notes to zset', + timestamp: Date.UTC(2017, 2, 22), + method: function (callback) { + var progress = this.progress; + + batch.processSortedSet('users:joindate', function (ids, next) { + async.each(ids, function (uid, next) { + db.getObjectField('user:' + uid, 'moderationNote', function (err, moderationNote) { + if (err || !moderationNote) { + progress.incr(); + return next(err); + } + var note = { + uid: 1, + note: moderationNote, + timestamp: Date.now(), + }; + + progress.incr(); + db.sortedSetAdd('uid:' + uid + ':moderation:notes', note.timestamp, JSON.stringify(note), next); + }); + }, next); + }, { + progress: this.progress, + }, callback); + }, +}; diff --git a/src/upgrades/1.5.0/post_votes_zset.js b/src/upgrades/1.5.0/post_votes_zset.js new file mode 100644 index 0000000000..4b5795a4b8 --- /dev/null +++ b/src/upgrades/1.5.0/post_votes_zset.js @@ -0,0 +1,29 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); + +module.exports = { + name: 'New sorted set posts:votes', + timestamp: Date.UTC(2017, 1, 27), + method: function (callback) { + var progress = this.progress; + + require('../../batch').processSortedSet('posts:pid', function (pids, next) { + async.each(pids, function (pid, next) { + db.getObjectFields('post:' + pid, ['upvotes', 'downvotes'], function (err, postData) { + if (err || !postData) { + return next(err); + } + + progress.incr(); + var votes = parseInt(postData.upvotes || 0, 10) - parseInt(postData.downvotes || 0, 10); + db.sortedSetAdd('posts:votes', votes, pid, next); + }); + }, next); + }, { + progress: this.progress, + }, callback); + }, +}; diff --git a/src/upgrades/1.5.0/remove_relative_uploaded_profile_cover.js b/src/upgrades/1.5.0/remove_relative_uploaded_profile_cover.js new file mode 100644 index 0000000000..fc8eb542f6 --- /dev/null +++ b/src/upgrades/1.5.0/remove_relative_uploaded_profile_cover.js @@ -0,0 +1,36 @@ +'use strict'; + +var db = require('../../database'); +var batch = require('../../batch'); + +var async = require('async'); + +module.exports = { + name: 'Remove relative_path from uploaded profile cover urls', + timestamp: Date.UTC(2017, 3, 26), + method: function (callback) { + var progress = this.progress; + + batch.processSortedSet('users:joindate', function (ids, done) { + async.each(ids, function (uid, cb) { + async.waterfall([ + function (next) { + db.getObjectField('user:' + uid, 'cover:url', next); + }, + function (url, next) { + progress.incr(); + + if (!url) { + return next(); + } + + var newUrl = url.replace(/^.*?\/uploads\//, '/assets/uploads/'); + db.setObjectField('user:' + uid, 'cover:url', newUrl, next); + }, + ], cb); + }, done); + }, { + progress: this.progress, + }, callback); + }, +}; diff --git a/src/upgrades/TEMPLATE b/src/upgrades/TEMPLATE new file mode 100644 index 0000000000..9618bc4f9e --- /dev/null +++ b/src/upgrades/TEMPLATE @@ -0,0 +1,14 @@ +'use strict'; + +var db = require('../../database'); + +var async = require('async'); +var winston = require('winston'); + +module.exports = { + name: 'User_friendly_upgrade_script_name', + timestamp: Date.UTC(2017, 0, 1), + method: function (callback) { + // Do stuff here... + }, +}; diff --git a/src/user.js b/src/user.js index 48098feadf..d521b77f0d 100644 --- a/src/user.js +++ b/src/user.js @@ -202,6 +202,16 @@ User.isGlobalModerator = function (uid, callback) { privileges.users.isGlobalModerator(uid, callback); }; +User.isPrivileged = function (uid, callback) { + async.parallel([ + async.apply(User.isAdministrator, uid), + async.apply(User.isGlobalModerator, uid), + async.apply(User.isModeratorOfAnyCategory, uid), + ], function (err, results) { + callback(err, results ? results.some(Boolean) : false); + }); +}; + User.isAdminOrGlobalMod = function (uid, callback) { async.parallel({ isAdmin: async.apply(User.isAdministrator, uid), diff --git a/src/user/admin.js b/src/user/admin.js index 1d6cd8c7ad..d463523f89 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -3,7 +3,6 @@ var async = require('async'); var db = require('../database'); -var posts = require('../posts'); var plugins = require('../plugins'); var winston = require('winston'); @@ -48,14 +47,4 @@ module.exports = function (User) { }, ], callback); }; - - User.resetFlags = function (uids, callback) { - if (!Array.isArray(uids) || !uids.length) { - return callback(); - } - - async.eachSeries(uids, function (uid, next) { - posts.dismissUserFlags(uid, next); - }, callback); - }; }; diff --git a/src/user/auth.js b/src/user/auth.js index 29a79f39c4..f1ac386136 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -11,6 +11,9 @@ module.exports = function (User) { User.auth = {}; User.auth.logAttempt = function (uid, ip, callback) { + if (!parseInt(uid, 10)) { + return setImmediate(callback); + } async.waterfall([ function (next) { db.exists('lockout:' + uid, next); @@ -65,7 +68,7 @@ module.exports = function (User) { } async.waterfall([ - async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':sessions', 0, -1), + async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':sessions', 0, 19), function (sids, next) { _sids = sids; async.map(sids, db.sessionStore.get.bind(db.sessionStore), next); diff --git a/src/user/bans.js b/src/user/bans.js index dc70d012dd..1795fc64e9 100644 --- a/src/user/bans.js +++ b/src/user/bans.js @@ -67,7 +67,7 @@ module.exports = function (User) { } // If they are banned, see if the ban has expired - var stillBanned = !userData['banned:expire'] || Date.now() < userData['banned:expire']; + var stillBanned = !parseInt(userData['banned:expire'], 10) || Date.now() < parseInt(userData['banned:expire'], 10); if (stillBanned) { return next(null, true); diff --git a/src/user/create.js b/src/user/create.js index e9cda9bce2..99cffc397f 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -140,7 +140,7 @@ module.exports = function (User) { if (userNameChanged) { User.notifications.sendNameChangeNotification(userData.uid, userData.username); } - plugins.fireHook('action:user.create', userData); + plugins.fireHook('action:user.create', { user: userData }); next(null, userData.uid); }, ], callback); diff --git a/src/user/data.js b/src/user/data.js index d0504ffce6..ca8dbd758f 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -26,6 +26,10 @@ module.exports = function (User) { }; User.getUsersFields = function (uids, fields, callback) { + if (!Array.isArray(uids) || !uids.length) { + return callback(null, []); + } + var fieldsToRemove = []; function addField(field) { if (fields.indexOf(field) === -1) { @@ -34,14 +38,6 @@ module.exports = function (User) { } } - if (!Array.isArray(uids) || !uids.length) { - return callback(null, []); - } - - var keys = uids.map(function (uid) { - return 'user:' + uid; - }); - if (fields.indexOf('uid') === -1) { fields.push('uid'); } @@ -55,11 +51,17 @@ module.exports = function (User) { addField('lastonline'); } + var uniqueUids = uids.filter(function (uid, index) { + return index === uids.indexOf(uid); + }); + async.waterfall([ function (next) { - db.getObjectsFields(keys, fields, next); + db.getObjectsFields(uidsToUserKeys(uniqueUids), fields, next); }, function (users, next) { + users = uidsToUsers(uids, uniqueUids, users); + modifyUserData(users, fieldsToRemove, next); }, ], callback); @@ -81,20 +83,39 @@ module.exports = function (User) { return callback(null, []); } - var keys = uids.map(function (uid) { - return 'user:' + uid; + var uniqueUids = uids.filter(function (uid, index) { + return index === uids.indexOf(uid); }); async.waterfall([ function (next) { - db.getObjects(keys, next); + db.getObjects(uidsToUserKeys(uniqueUids), next); }, function (users, next) { + users = uidsToUsers(uids, uniqueUids, users); + modifyUserData(users, [], next); }, ], callback); }; + function uidsToUsers(uids, uniqueUids, usersData) { + var ref = uniqueUids.reduce(function (memo, cur, idx) { + memo[cur] = idx; + return memo; + }, {}); + var users = uids.map(function (uid) { + return usersData[ref[uid]]; + }); + return users; + } + + function uidsToUserKeys(uids) { + return uids.map(function (uid) { + return 'user:' + uid; + }); + } + function modifyUserData(users, fieldsToRemove, callback) { users.forEach(function (user) { if (!user) { diff --git a/src/user/info.js b/src/user/info.js index 4af1b77f35..755c7cff8c 100644 --- a/src/user/info.js +++ b/src/user/info.js @@ -7,6 +7,7 @@ var validator = require('validator'); var db = require('../database'); var posts = require('../posts'); var topics = require('../topics'); +var utils = require('../../public/src/utils'); module.exports = function (User) { User.getLatestBanInfo = function (uid, callback) { @@ -40,7 +41,7 @@ module.exports = function (User) { uid: uid, timestamp: timestamp, expiry: parseInt(expiry, 10), - expiry_readable: new Date(parseInt(expiry, 10)).toString().replace(/:/g, '%3A'), + expiry_readable: new Date(parseInt(expiry, 10)).toString(), reason: validator.escape(String(reason)), }); }); @@ -50,7 +51,7 @@ module.exports = function (User) { async.waterfall([ function (next) { async.parallel({ - flags: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':flag:pids', 0, 19), + flags: async.apply(db.getSortedSetRevRangeWithScores, 'flags:byTargetUid:' + uid, 0, 19), bans: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':bans', 0, 19), reasons: async.apply(db.getSortedSetRevRangeWithScores, 'banned:' + uid + ':reasons', 0, 19), }, next); @@ -74,7 +75,7 @@ module.exports = function (User) { } callback(null, data.map(function (set) { set.timestamp = set.score; - set.timestampISO = new Date(set.score).toISOString(); + set.timestampISO = utils.toISOString(set.score); set.value = validator.escape(String(set.value.split(':')[0])); delete set.score; return set; @@ -138,4 +139,36 @@ module.exports = function (User) { return banObj; }); } + + User.getModerationNotes = function (uid, start, stop, callback) { + var noteData; + async.waterfall([ + function (next) { + db.getSortedSetRevRange('uid:' + uid + ':moderation:notes', start, stop, next); + }, + function (notes, next) { + var uids = []; + noteData = notes.map(function (note) { + try { + var data = JSON.parse(note); + uids.push(data.uid); + data.timestampISO = utils.toISOString(data.timestamp); + return data; + } catch (err) { + return next(err); + } + }); + + User.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture'], next); + }, + function (userData, next) { + noteData.forEach(function (note, index) { + if (note) { + note.user = userData[index]; + } + }); + next(null, noteData); + }, + ], callback); + }; }; diff --git a/src/user/notifications.js b/src/user/notifications.js index f2b4bb03fd..318794fb20 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -32,20 +32,80 @@ var privileges = require('../privileges'); }); }; - UserNotifications.getAll = function (uid, start, stop, callback) { - getNotifications(uid, start, stop, function (err, notifs) { - if (err) { - return callback(err); - } - notifs = notifs.unread.concat(notifs.read); - notifs = notifs.filter(Boolean).sort(function (a, b) { - return b.datetime - a.datetime; - }); + function filterNotifications(nids, filter, callback) { + if (!filter) { + return setImmediate(callback, null, nids); + } + async.waterfall([ + function (next) { + var keys = nids.map(function (nid) { + return 'notifications:' + nid; + }); + db.getObjectsFields(keys, ['nid', 'type'], next); + }, + function (notifications, next) { + nids = notifications.filter(function (notification) { + return notification && notification.nid && notification.type === filter; + }).map(function (notification) { + return notification.nid; + }); + next(null, nids); + }, + ], callback); + } - callback(null, notifs); - }); + UserNotifications.getAll = function (uid, filter, callback) { + var nids; + async.waterfall([ + function (next) { + async.parallel({ + unread: function (next) { + db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, -1, next); + }, + read: function (next) { + db.getSortedSetRevRange('uid:' + uid + ':notifications:read', 0, -1, next); + }, + }, next); + }, + function (results, next) { + nids = results.unread.concat(results.read); + db.isSortedSetMembers('notifications', nids, next); + }, + function (exists, next) { + var deleteNids = []; + + nids = nids.filter(function (nid, index) { + if (!nid || !exists[index]) { + deleteNids.push(nid); + } + return nid && exists[index]; + }); + + deleteUserNids(deleteNids, uid, next); + }, + function (next) { + filterNotifications(nids, filter, next); + }, + ], callback); }; + function deleteUserNids(nids, uid, callback) { + callback = callback || function () {}; + if (!nids.length) { + return setImmediate(callback); + } + async.parallel([ + function (next) { + db.sortedSetRemove('uid:' + uid + ':notifications:read', nids, next); + }, + function (next) { + db.sortedSetRemove('uid:' + uid + ':notifications:unread', nids, next); + }, + ], function (err) { + callback(err); + }); + } + function getNotifications(uid, start, stop, callback) { async.parallel({ unread: function (next) { @@ -58,53 +118,55 @@ var privileges = require('../privileges'); } function getNotificationsFromSet(set, read, uid, start, stop, callback) { - var setNids; - async.waterfall([ - async.apply(db.getSortedSetRevRange, set, start, stop), + function (next) { + db.getSortedSetRevRange(set, start, stop, next); + }, function (nids, next) { if (!Array.isArray(nids) || !nids.length) { return callback(null, []); } - setNids = nids; UserNotifications.getNotifications(nids, uid, next); }, - function (notifs, next) { - var deletedNids = []; + ], callback); + } - notifs.forEach(function (notification, index) { - if (!notification) { - winston.verbose('[notifications.get] nid ' + setNids[index] + ' not found. Removing.'); - deletedNids.push(setNids[index]); - } else { - notification.read = read; + UserNotifications.getNotifications = function (nids, uid, callback) { + var notificationData = []; + async.waterfall([ + function (next) { + async.parallel({ + notifications: function (next) { + notifications.getMultiple(nids, next); + }, + hasRead: function (next) { + db.isSortedSetMembers('uid:' + uid + ':notifications:read', nids, next); + }, + }, next); + }, + function (results, next) { + var deletedNids = []; + notificationData = results.notifications.filter(function (notification, index) { + if (!notification || !notification.nid) { + deletedNids.push(nids[index]); + } + if (notification) { + notification.read = results.hasRead[index]; notification.readClass = !notification.read ? 'unread' : ''; } - }); - if (deletedNids.length) { - db.sortedSetRemove(set, deletedNids); - } + return notification && notification.path; + }); - notifications.merge(notifs, next); + deleteUserNids(deletedNids, uid, next); + }, + function (next) { + notifications.merge(notificationData, next); }, ], callback); - } - - UserNotifications.getNotifications = function (nids, uid, callback) { - notifications.getMultiple(nids, function (err, notifications) { - if (err) { - return callback(err); - } - notifications = notifications.filter(function (notification) { - return notification && notification.path; - }); - callback(null, notifications); - }); }; - UserNotifications.getDailyUnread = function (uid, callback) { var yesterday = Date.now() - (1000 * 60 * 60 * 24); // Approximate, can be more or less depending on time changes, makes no difference really. @@ -223,6 +285,7 @@ var privileges = require('../privileges'); } notifications.create({ + type: 'new-topic', bodyShort: '[[notifications:user_posted_topic, ' + postData.user.username + ', ' + title + ']]', bodyLong: postData.content, pid: postData.pid, diff --git a/src/user/picture.js b/src/user/picture.js index 2b4f45d344..dfe0aa72bb 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -154,7 +154,7 @@ module.exports = function (User) { function (path, next) { picture.path = path; - var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; + var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 200; image.resizeImage({ path: picture.path, extension: extension, diff --git a/src/user/profile.js b/src/user/profile.js index e6ac5adf0f..8347d6de74 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -15,6 +15,9 @@ module.exports = function (User) { var fields = ['username', 'email', 'fullname', 'website', 'location', 'groupTitle', 'birthday', 'signature', 'aboutme']; + var updateUid = data.uid; + var oldData; + if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) { return callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]')); } @@ -32,14 +35,18 @@ module.exports = function (User) { data = data.data; async.series([ - async.apply(isEmailAvailable, data, uid), - async.apply(isUsernameAvailable, data, uid), + async.apply(isEmailAvailable, data, updateUid), + async.apply(isUsernameAvailable, data, updateUid), async.apply(isGroupTitleValid, data), ], function (err) { next(err); }); }, function (next) { + User.getUserFields(updateUid, fields, next); + }, + function (_oldData, next) { + oldData = _oldData; async.each(fields, function (field, next) { if (!(data[field] !== undefined && typeof data[field] === 'string')) { return next(); @@ -48,21 +55,21 @@ module.exports = function (User) { data[field] = data[field].trim(); if (field === 'email') { - return updateEmail(uid, data.email, next); + return updateEmail(updateUid, data.email, next); } else if (field === 'username') { - return updateUsername(uid, data.username, next); + return updateUsername(updateUid, data.username, next); } else if (field === 'fullname') { - return updateFullname(uid, data.fullname, next); + return updateFullname(updateUid, data.fullname, next); } else if (field === 'signature') { data[field] = S(data[field]).stripTags().s; } - User.setUserField(uid, field, data[field], next); + User.setUserField(updateUid, field, data[field], next); }, next); }, function (next) { - plugins.fireHook('action:user.updateProfile', { data: data, uid: uid }); - User.getUserFields(uid, ['email', 'username', 'userslug', 'picture', 'icon:text', 'icon:bgColor'], next); + plugins.fireHook('action:user.updateProfile', { uid: uid, data: data, fields: fields, oldData: oldData }); + User.getUserFields(updateUid, ['email', 'username', 'userslug', 'picture', 'icon:text', 'icon:bgColor'], next); }, ], callback); }; diff --git a/src/user/settings.js b/src/user/settings.js index bef20e0087..de0da0641d 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -2,83 +2,122 @@ 'use strict'; var async = require('async'); +var _ = require('underscore'); + var meta = require('../meta'); var db = require('../database'); var plugins = require('../plugins'); +var LRU = require('lru-cache'); + +var cache = LRU({ + max: 1000, + length: function () { return 1; }, + maxAge: 1000 * 60 * 60, +}); + module.exports = function (User) { + User.settingsCache = cache; + User.getSettings = function (uid, callback) { if (!parseInt(uid, 10)) { return onSettingsLoaded(0, {}, callback); } - db.getObject('user:' + uid + ':settings', function (err, settings) { - if (err) { - return callback(err); - } + var cached = cache.get('user:' + uid + ':settings'); + if (cached) { + return onSettingsLoaded(uid, _.clone(cached || {}), callback); + } - onSettingsLoaded(uid, settings || {}, callback); - }); + async.waterfall([ + function (next) { + db.getObject('user:' + uid + ':settings', next); + }, + function (settings, next) { + settings = settings || {}; + settings.uid = uid; + cache.set('user:' + uid + ':settings', settings); + onSettingsLoaded(uid, _.clone(settings || {}), next); + }, + ], callback); }; User.getMultipleUserSettings = function (uids, callback) { + function getFromCache(next) { + var settings = uids.map(function (uid) { + return cache.get('user:' + uid + ':settings') || {}; + }); + async.map(settings, function (setting, next) { + onSettingsLoaded(setting.uid, _.clone(setting), next); + }, next); + } + + if (!Array.isArray(uids) || !uids.length) { return callback(null, []); } - var keys = uids.map(function (uid) { - return 'user:' + uid + ':settings'; + var nonCachedUids = uids.filter(function (uid) { + return !cache.has('user:' + uid + ':settings'); }); - db.getObjects(keys, function (err, settings) { - if (err) { - return callback(err); - } - - for (var i = 0; i < settings.length; i += 1) { - settings[i] = settings[i] || {}; - settings[i].uid = uids[i]; - } + if (!nonCachedUids.length) { + return getFromCache(callback); + } - async.map(settings, function (setting, next) { - onSettingsLoaded(setting.uid, setting, next); - }, callback); + var keys = nonCachedUids.map(function (uid) { + return 'user:' + uid + ':settings'; }); + + async.waterfall([ + function (next) { + db.getObjects(keys, next); + }, + function (settings, next) { + settings.forEach(function (userSettings, index) { + userSettings = userSettings || {}; + userSettings.uid = nonCachedUids[index]; + cache.set('user:' + userSettings.uid + ':settings', userSettings); + }); + + getFromCache(next); + }, + ], callback); }; function onSettingsLoaded(uid, settings, callback) { - plugins.fireHook('filter:user.getSettings', { uid: uid, settings: settings }, function (err, data) { - if (err) { - return callback(err); - } - - settings = data.settings; - - var defaultTopicsPerPage = parseInt(meta.config.topicsPerPage, 10) || 20; - var defaultPostsPerPage = parseInt(meta.config.postsPerPage, 10) || 20; - - settings.showemail = parseInt(getSetting(settings, 'showemail', 0), 10) === 1; - settings.showfullname = parseInt(getSetting(settings, 'showfullname', 0), 10) === 1; - settings.openOutgoingLinksInNewTab = parseInt(getSetting(settings, 'openOutgoingLinksInNewTab', 0), 10) === 1; - settings.dailyDigestFreq = getSetting(settings, 'dailyDigestFreq', 'off'); - settings.usePagination = parseInt(getSetting(settings, 'usePagination', 0), 10) === 1; - settings.topicsPerPage = Math.min(settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : defaultTopicsPerPage, defaultTopicsPerPage); - settings.postsPerPage = Math.min(settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : defaultPostsPerPage, defaultPostsPerPage); - settings.userLang = settings.userLang || meta.config.defaultLang || 'en-GB'; - settings.topicPostSort = getSetting(settings, 'topicPostSort', 'oldest_to_newest'); - settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'newest_to_oldest'); - settings.followTopicsOnCreate = parseInt(getSetting(settings, 'followTopicsOnCreate', 1), 10) === 1; - settings.followTopicsOnReply = parseInt(getSetting(settings, 'followTopicsOnReply', 0), 10) === 1; - settings.sendChatNotifications = parseInt(getSetting(settings, 'sendChatNotifications', 0), 10) === 1; - settings.sendPostNotifications = parseInt(getSetting(settings, 'sendPostNotifications', 0), 10) === 1; - settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; - settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; - settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1; - settings.bootswatchSkin = settings.bootswatchSkin || meta.config.bootswatchSkin || 'default'; - settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1; - - callback(null, settings); - }); + async.waterfall([ + function (next) { + plugins.fireHook('filter:user.getSettings', { uid: uid, settings: settings }, next); + }, + function (data, next) { + settings = data.settings; + + var defaultTopicsPerPage = parseInt(meta.config.topicsPerPage, 10) || 20; + var defaultPostsPerPage = parseInt(meta.config.postsPerPage, 10) || 20; + + settings.showemail = parseInt(getSetting(settings, 'showemail', 0), 10) === 1; + settings.showfullname = parseInt(getSetting(settings, 'showfullname', 0), 10) === 1; + settings.openOutgoingLinksInNewTab = parseInt(getSetting(settings, 'openOutgoingLinksInNewTab', 0), 10) === 1; + settings.dailyDigestFreq = getSetting(settings, 'dailyDigestFreq', 'off'); + settings.usePagination = parseInt(getSetting(settings, 'usePagination', 0), 10) === 1; + settings.topicsPerPage = Math.min(settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : defaultTopicsPerPage, defaultTopicsPerPage); + settings.postsPerPage = Math.min(settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : defaultPostsPerPage, defaultPostsPerPage); + settings.userLang = settings.userLang || meta.config.defaultLang || 'en-GB'; + settings.topicPostSort = getSetting(settings, 'topicPostSort', 'oldest_to_newest'); + settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'newest_to_oldest'); + settings.followTopicsOnCreate = parseInt(getSetting(settings, 'followTopicsOnCreate', 1), 10) === 1; + settings.followTopicsOnReply = parseInt(getSetting(settings, 'followTopicsOnReply', 0), 10) === 1; + settings.sendChatNotifications = parseInt(getSetting(settings, 'sendChatNotifications', 0), 10) === 1; + settings.sendPostNotifications = parseInt(getSetting(settings, 'sendPostNotifications', 0), 10) === 1; + settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; + settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; + settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1; + settings.bootswatchSkin = settings.bootswatchSkin || meta.config.bootswatchSkin || 'default'; + settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1; + next(null, settings); + }, + ], callback); } function getSetting(settings, key, defaultValue) { @@ -138,6 +177,7 @@ module.exports = function (User) { User.updateDigestSetting(uid, data.dailyDigestFreq, next); }, function (next) { + cache.del('user:' + uid + ':settings'); User.getSettings(uid, next); }, ], callback); @@ -160,8 +200,9 @@ module.exports = function (User) { User.setSetting = function (uid, key, value, callback) { if (!parseInt(uid, 10)) { - return callback(); + return setImmediate(callback); } + cache.del('user:' + uid + ':settings'); db.setObjectField('user:' + uid + ':settings', key, value, callback); }; }; diff --git a/src/views/admin/advanced/cache.tpl b/src/views/admin/advanced/cache.tpl index 126cba8db0..72854a34b0 100644 --- a/src/views/admin/advanced/cache.tpl +++ b/src/views/admin/advanced/cache.tpl @@ -26,6 +26,24 @@
+
+
User Settings Cache
+
+ +
+ {userSettingsCache.itemCount}
+ +
+ {userSettingsCache.length} / {userSettingsCache.max}
+ +
+
+ [[admin/advanced/cache:percent-full, {userSettingsCache.percentFull}]] +
+
+
+
+
Group Cache
diff --git a/src/views/admin/advanced/database.tpl b/src/views/admin/advanced/database.tpl index c9003e52f9..9519ce9141 100644 --- a/src/views/admin/advanced/database.tpl +++ b/src/views/admin/advanced/database.tpl @@ -13,16 +13,16 @@ [[admin/advanced/database:mongo.objects]] {mongo.objects}
[[admin/advanced/database:mongo.avg-object-size]] [[admin/advanced/database:x-b, {mongo.avgObjSize}]]

- [[admin/advanced/database:mongo.data-size]] [[admin/advanced/database:x-mb, {mongo.dataSize}]]
- [[admin/advanced/database:mongo.storage-size]] [[admin/advanced/database:x-mb, {mongo.storageSize}]]
- [[admin/advanced/database:mongo.index-size]] [[admin/advanced/database:x-mb, {mongo.indexSize}]]
+ [[admin/advanced/database:mongo.data-size]] [[admin/advanced/database:x-gb, {mongo.dataSize}]]
+ [[admin/advanced/database:mongo.storage-size]] [[admin/advanced/database:x-gb, {mongo.storageSize}]]
+ [[admin/advanced/database:mongo.index-size]] [[admin/advanced/database:x-gb, {mongo.indexSize}]]
- [[admin/advanced/database:mongo.file-size]] [[admin/advanced/database:x-mb, {mongo.fileSize}]]
+ [[admin/advanced/database:mongo.file-size]] [[admin/advanced/database:x-gb, {mongo.fileSize}]]

- [[admin/advanced/database:mongo.resident-memory]] [[admin/advanced/database:x-mb, {mongo.mem.resident}]]
- [[admin/advanced/database:mongo.virtual-memory]] [[admin/advanced/database:x-mb, {mongo.mem.virtual}]]
- [[admin/advanced/database:mongo.mapped-memory]] [[admin/advanced/database:x-mb, {mongo.mem.mapped}]]
+ [[admin/advanced/database:mongo.resident-memory]] [[admin/advanced/database:x-gb, {mongo.mem.resident}]]
+ [[admin/advanced/database:mongo.virtual-memory]] [[admin/advanced/database:x-gb, {mongo.mem.virtual}]]
+ [[admin/advanced/database:mongo.mapped-memory]] [[admin/advanced/database:x-gb, {mongo.mem.mapped}]]
@@ -43,7 +43,7 @@ [[admin/advanced/database:redis.blocked-clients]] {redis.blocked_clients}

- [[admin/advanced/database:redis.used-memory]] {redis.used_memory_human}
+ [[admin/advanced/database:redis.used-memory]] [[admin/advanced/database:x-gb, {redis.used_memory_human}]]
[[admin/advanced/database:redis.memory-frag-ratio]] {redis.mem_fragmentation_ratio}

[[admin/advanced/database:redis.total-connections-recieved]] {redis.total_connections_received}
diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl index 50963d4d8c..f594e7a499 100644 --- a/src/views/admin/development/info.tpl +++ b/src/views/admin/development/info.tpl @@ -5,6 +5,8 @@
+ [[admin/development/info:nodes-responded, {nodeCount}, {timeout}]] + @@ -24,8 +26,8 @@ diff --git a/src/views/admin/general/dashboard.tpl b/src/views/admin/general/dashboard.tpl index cc1d8d97ce..d904637fe4 100644 --- a/src/views/admin/general/dashboard.tpl +++ b/src/views/admin/general/dashboard.tpl @@ -11,17 +11,17 @@
-
+ - @@ -29,25 +29,25 @@
-
+
{stats.name}
{stats.day} -
[[admin/general/dashboard:stats.day]]
+
[[admin/general/dashboard:stats.day]]
{stats.week} -
[[admin/general/dashboard:stats.week]]
+
[[admin/general/dashboard:stats.week]]
{stats.month} -
[[admin/general/dashboard:stats.month]]
+
[[admin/general/dashboard:stats.month]]
{stats.alltime} -
[[admin/general/dashboard:stats.all]]
+
[[admin/general/dashboard:stats.all]]
@@ -115,7 +115,7 @@
[[admin/general/dashboard:active-users]]
-
+
diff --git a/src/views/admin/general/languages.tpl b/src/views/admin/general/languages.tpl index 310d1a366d..747c5d43af 100644 --- a/src/views/admin/general/languages.tpl +++ b/src/views/admin/general/languages.tpl @@ -16,6 +16,17 @@
+ +
+
+
+ +
+
+
diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl deleted file mode 100644 index a6351009d8..0000000000 --- a/src/views/admin/manage/flags.tpl +++ /dev/null @@ -1,219 +0,0 @@ -
-
- -
-
-
-
-

- -

-
- -
-
- - -
-
-
- - -
-
-
- -
-
-
- - -
-
-
- -
- -
-
- -
-
-
- - - - - -
- -
- -
- -
- [[admin/manage/flags:none-flagged]] -
- - - -
- -
-
-
-
-
- - - - -
{../user.icon:text}
- -
- - - {../user.username} - -
-

{posts.content}

-
- - - [[admin/manage/flags:posted-in]] {posts.category.name} - • - [[admin/manage/flags:read-more]] - - -
-
-
- - [[admin/manage/flags:flagged-x-times, {posts.flags}]] -
- -
-
- - -
-
-
-
-
-
-
-
- - -
-
- - -
-
- - -
- - -
-
-
[[topic:flag_manage_history]]
- -
- [[topic:flag_manage_no_history]] -
- -
    - -
  • -
    - - - -
    {../user.icon:text}
    - - [[topic:flag_manage_history_{posts.flagData.history.type}, {posts.flagData.history.label}]] -
  • - -
- -
-
-
-
-
- - -
-
-
-
diff --git a/src/views/admin/manage/groups.tpl b/src/views/admin/manage/groups.tpl index 98e08be2e7..44c4187604 100644 --- a/src/views/admin/manage/groups.tpl +++ b/src/views/admin/manage/groups.tpl @@ -9,6 +9,7 @@
+ @@ -24,6 +25,9 @@ +
{info.process.pid} {info.process.version} - {info.stats.onlineRegisteredCount} / - {info.stats.onlineGuestCount} / + {info.stats.onlineRegisteredCount} / + {info.stats.onlineGuestCount} / {info.stats.socketCount} {info.git.branch}@{info.git.hash}
[[admin/manage/groups:name]]
diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index 7d7fdbbe28..6a16141aca 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -19,7 +19,6 @@
  • [[admin/manage/users:temp-ban]]
  • [[admin/manage/users:unban]]
  • [[admin/manage/users:reset-lockout]]
  • -
  • [[admin/manage/users:reset-flags]]
  • [[admin/manage/users:delete]]
  • [[admin/manage/users:purge]]
  • diff --git a/src/views/admin/partials/menu.tpl b/src/views/admin/partials/menu.tpl index d8cb8acc5b..ee1eb8f4a0 100644 --- a/src/views/admin/partials/menu.tpl +++ b/src/views/admin/partials/menu.tpl @@ -19,7 +19,6 @@
  • [[admin/menu:manage/users]]
  • [[admin/menu:manage/registration]]
  • [[admin/menu:manage/groups]]
  • -
  • [[admin/menu:manage/flags]]
  • [[admin/menu:manage/ip-blacklist]]
  • @@ -192,7 +191,6 @@
  • [[admin/menu:manage/users]]
  • [[admin/menu:manage/registration]]
  • [[admin/menu:manage/groups]]
  • -
  • [[admin/menu:manage/flags]]
  • [[admin/menu:manage/ip-blacklist]]
  • diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl index 9a96706394..2d9a198ec8 100644 --- a/src/views/admin/settings/general.tpl +++ b/src/views/admin/settings/general.tpl @@ -113,6 +113,23 @@
    +
    +
    [[admin/settings/general:search-default-sort-by]]
    +
    + +
    +
    +
    [[admin/settings/general:outgoing-links]]
    diff --git a/src/views/admin/settings/post.tpl b/src/views/admin/settings/post.tpl index 0eef74390c..18d31c0cdf 100644 --- a/src/views/admin/settings/post.tpl +++ b/src/views/admin/settings/post.tpl @@ -123,9 +123,23 @@
    - - -
    + + +
    + + + + +
    +
    [[admin/settings/post:recent]]
    +
    +
    +
    + +
    diff --git a/src/views/admin/settings/uploads.tpl b/src/views/admin/settings/uploads.tpl index feff2dbff3..32e432adfc 100644 --- a/src/views/admin/settings/uploads.tpl +++ b/src/views/admin/settings/uploads.tpl @@ -91,7 +91,7 @@
    - +

    [[admin/settings/uploads:profile-image-dimension-help]]

    diff --git a/src/views/emails/banned.tpl b/src/views/emails/banned.tpl new file mode 100644 index 0000000000..2384fe1549 --- /dev/null +++ b/src/views/emails/banned.tpl @@ -0,0 +1,18 @@ +

    + [[email:banned.text1, {username}, {site_title}]] + + [[email:banned.text2, {until}]] + +

    + + +

    + [[email:banned.text3]] +

    + +

    + {reason} +

    + + + \ No newline at end of file diff --git a/src/webserver.js b/src/webserver.js index 3911846b67..868eb4429c 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -133,6 +133,9 @@ function setupExpressApp(app, callback) { app.use(compression()); + app.get('/ping', ping); + app.get('/sping', ping); + setupFavicon(app); app.use(relativePath + '/apple-touch-icon', middleware.routeTouchIcon); @@ -162,6 +165,10 @@ function setupExpressApp(app, callback) { setupAutoLocale(app, callback); } +function ping(req, res) { + res.status(200).send(req.path === '/sping' ? 'healthy' : '200'); +} + function setupFavicon(app) { var faviconPath = meta.config['brand:favicon'] || 'favicon.ico'; faviconPath = path.join(nconf.get('base_dir'), 'public', faviconPath.replace(/assets\/uploads/, 'uploads')); @@ -206,7 +213,7 @@ function setupAutoLocale(app, callback) { }); app.use(function (req, res, next) { - if (parseInt(req.uid, 10) > 0) { + if (parseInt(req.uid, 10) > 0 || parseInt(meta.config.autoDetectLang, 10) !== 1) { return next(); } @@ -294,11 +301,11 @@ module.exports.testSocket = function (socketPath, callback) { var file = require('./file'); async.series([ function (next) { - file.exists(socketPath, function (exists) { + file.exists(socketPath, function (err, exists) { if (exists) { next(); } else { - callback(); + callback(err); } }); }, diff --git a/src/widgets/index.js b/src/widgets/index.js index 2e439c52d8..b671effa8e 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -43,13 +43,15 @@ widgets.render = function (uid, area, req, res, callback) { data: widget.data, req: req, res: res, - }, function (err, html) { - if (err || html === null) { + }, function (err, data) { + if (err || data === null) { return next(err); } - + var html = data; if (typeof html !== 'string') { - html = ''; + html = data.html; + } else { + winston.warn('[widgets.render] passing a string is deprecated!, filter:widget.render:' + widget.widget + '. Please set hookData.html in your plugin.'); } if (widget.data.container && widget.data.container.match('{body}')) { diff --git a/test/database/hash.js b/test/database/hash.js index 4da340b960..7f9dc305d0 100644 --- a/test/database/hash.js +++ b/test/database/hash.js @@ -42,6 +42,16 @@ describe('Hash methods', function () { }); }); }); + + it('should not error if a key is empty string', function (done) { + db.setObject('emptyField', { '': '', b: 1 }, function (err) { + assert.ifError(err); + db.getObject('emptyField', function (err, data) { + assert.ifError(err); + done(); + }); + }); + }); }); describe('setObjectField()', function () { diff --git a/test/database/sorted.js b/test/database/sorted.js index 939f4b259e..b90e9cf71f 100644 --- a/test/database/sorted.js +++ b/test/database/sorted.js @@ -324,6 +324,22 @@ describe('Sorted Set methods', function () { done(); }); }); + + it('should not error if key is undefined', function (done) { + db.sortedSetScore(undefined, 1, function (err, score) { + assert.ifError(err); + assert.strictEqual(score, null); + done(); + }); + }); + + it('should not error if value is undefined', function (done) { + db.sortedSetScore('sortedSetTest1', undefined, function (err, score) { + assert.ifError(err); + assert.strictEqual(score, null); + done(); + }); + }); }); describe('sortedSetsScore()', function () { @@ -335,6 +351,15 @@ describe('Sorted Set methods', function () { done(); }); }); + + it('should return scores even if some keys are undefined', function (done) { + db.sortedSetsScore(['sortedSetTest1', undefined, 'doesnotexist'], 'value1', function (err, scores) { + assert.equal(err, null); + assert.equal(arguments.length, 2); + assert.deepEqual(scores, [1.1, null, null]); + done(); + }); + }); }); describe('sortedSetScores()', function () { @@ -358,6 +383,15 @@ describe('Sorted Set methods', function () { done(); }); }); + + it('should return scores even if some values are undefined', function (done) { + db.sortedSetScores('sortedSetTest1', ['value2', undefined, 'doesnotexist'], function (err, scores) { + assert.equal(err, null); + assert.equal(arguments.length, 2); + assert.deepEqual(scores, [1.2, null, null]); + done(); + }); + }); }); describe('isSortedSetMember()', function () { diff --git a/test/flags.js b/test/flags.js new file mode 100644 index 0000000000..0426541cd8 --- /dev/null +++ b/test/flags.js @@ -0,0 +1,570 @@ +'use strict'; + +/* globals require, before, after, describe, it*/ + +var assert = require('assert'); +var async = require('async'); + +var db = require('./mocks/databasemock'); +var Flags = require('../src/flags'); +var Categories = require('../src/categories'); +var Topics = require('../src/topics'); +var Posts = require('../src/posts'); +var User = require('../src/user'); +var Groups = require('../src/groups'); +var Meta = require('../src/meta'); + +describe('Flags', function () { + before(function (done) { + // Create some stuff to flag + async.waterfall([ + async.apply(User.create, { username: 'testUser', password: 'abcdef', email: 'b@c.com' }), + function (uid, next) { + Categories.create({ + name: 'test category', + }, function (err, category) { + if (err) { + return done(err); + } + + Topics.post({ + cid: category.cid, + uid: uid, + title: 'Topic to flag', + content: 'This is flaggable content', + }, next); + }); + }, + function (topicData, next) { + User.create({ + username: 'testUser2', password: 'abcdef', email: 'c@d.com', + }, next); + }, + function (uid, next) { + Groups.join('administrators', uid, next); + }, + function (next) { + User.create({ + username: 'unprivileged', password: 'abcdef', email: 'd@e.com', + }, next); + }, + ], done); + }); + + describe('.create()', function () { + it('should create a flag and return its data', function (done) { + Flags.create('post', 1, 1, 'Test flag', function (err, flagData) { + assert.ifError(err); + var compare = { + flagId: 1, + uid: 1, + targetId: 1, + type: 'post', + description: 'Test flag', + }; + + for (var key in compare) { + if (compare.hasOwnProperty(key)) { + assert.ok(flagData[key]); + assert.equal(flagData[key], compare[key]); + } + } + + done(); + }); + }); + + it('should add the flag to the byCid zset for category 1 if it is of type post', function (done) { + db.isSortedSetMember('flags:byCid:' + 1, 1, function (err, isMember) { + assert.ifError(err); + assert.ok(isMember); + done(); + }); + }); + + it('should add the flag to the byPid zset for pid 1 if it is of type post', function (done) { + db.isSortedSetMember('flags:byPid:' + 1, 1, function (err, isMember) { + assert.ifError(err); + assert.ok(isMember); + done(); + }); + }); + }); + + describe('.exists()', function () { + it('should return Boolean True if a flag matching the flag hash already exists', function (done) { + Flags.exists('post', 1, 1, function (err, exists) { + assert.ifError(err); + assert.strictEqual(true, exists); + done(); + }); + }); + + it('should return Boolean False if a flag matching the flag hash does not already exists', function (done) { + Flags.exists('post', 1, 2, function (err, exists) { + assert.ifError(err); + assert.strictEqual(false, exists); + done(); + }); + }); + }); + + describe('.targetExists()', function () { + it('should return Boolean True if the targeted element exists', function (done) { + Flags.targetExists('post', 1, function (err, exists) { + assert.ifError(err); + assert.strictEqual(true, exists); + done(); + }); + }); + + it('should return Boolean False if the targeted element does not exist', function (done) { + Flags.targetExists('post', 15, function (err, exists) { + assert.ifError(err); + assert.strictEqual(false, exists); + done(); + }); + }); + }); + + describe('.get()', function () { + it('should retrieve and display a flag\'s data', function (done) { + Flags.get(1, function (err, flagData) { + assert.ifError(err); + var compare = { + flagId: 1, + uid: 1, + targetId: 1, + type: 'post', + description: 'Test flag', + state: 'open', + }; + + for (var key in compare) { + if (compare.hasOwnProperty(key)) { + assert.ok(flagData[key]); + assert.equal(flagData[key], compare[key]); + } + } + + done(); + }); + }); + }); + + describe('.list()', function () { + it('should show a list of flags (with one item)', function (done) { + Flags.list({}, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.equal(flags.length, 1); + + Flags.get(flags[0].flagId, function (err, flagData) { + assert.ifError(err); + assert.equal(flags[0].flagId, flagData.flagId); + assert.equal(flags[0].description, flagData.description); + done(); + }); + }); + }); + + describe('(with filters)', function () { + it('should return a filtered list of flags if said filters are passed in', function (done) { + Flags.list({ + state: 'open', + }, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.strictEqual(1, parseInt(flags[0].flagId, 10)); + done(); + }); + }); + + it('should return no flags if a filter with no matching flags is used', function (done) { + Flags.list({ + state: 'rejected', + }, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.strictEqual(0, flags.length); + done(); + }); + }); + + it('should return a flag when filtered by cid 1', function (done) { + Flags.list({ + cid: 1, + }, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.strictEqual(1, flags.length); + done(); + }); + }); + + it('shouldn\'t return a flag when filtered by cid 2', function (done) { + Flags.list({ + cid: 2, + }, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.strictEqual(0, flags.length); + done(); + }); + }); + + it('should return a flag when filtered by both cid 1 and 2', function (done) { + Flags.list({ + cid: [1, 2], + }, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.strictEqual(1, flags.length); + done(); + }); + }); + + it('should return one flag if filtered by both cid 1 and 2 and open state', function (done) { + Flags.list({ + cid: [1, 2], + state: 'open', + }, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.strictEqual(1, flags.length); + done(); + }); + }); + + it('should return no flag if filtered by both cid 1 and 2 and non-open state', function (done) { + Flags.list({ + cid: [1, 2], + state: 'resolved', + }, 1, function (err, flags) { + assert.ifError(err); + assert.ok(Array.isArray(flags)); + assert.strictEqual(0, flags.length); + done(); + }); + }); + }); + }); + + describe('.update()', function () { + it('should alter a flag\'s various attributes and persist them to the database', function (done) { + Flags.update(1, 1, { + state: 'wip', + assignee: 1, + }, function (err) { + assert.ifError(err); + db.getObjectFields('flag:1', ['state', 'assignee'], function (err, data) { + if (err) { + throw err; + } + + assert.strictEqual('wip', data.state); + assert.ok(!isNaN(parseInt(data.assignee, 10))); + assert.strictEqual(1, parseInt(data.assignee, 10)); + done(); + }); + }); + }); + + it('should persist to the flag\'s history', function (done) { + Flags.getHistory(1, function (err, history) { + if (err) { + throw err; + } + + history.forEach(function (change) { + switch (change.attribute) { + case 'state': + assert.strictEqual('[[flags:state-wip]]', change.value); + break; + + case 'assignee': + assert.strictEqual(1, change.value); + break; + } + }); + + done(); + }); + }); + }); + + describe('.getTarget()', function () { + it('should return a post\'s data if queried with type "post"', function (done) { + Flags.getTarget('post', 1, 1, function (err, data) { + assert.ifError(err); + var compare = { + uid: 1, + pid: 1, + content: 'This is flaggable content', + }; + + for (var key in compare) { + if (compare.hasOwnProperty(key)) { + assert.ok(data[key]); + assert.equal(data[key], compare[key]); + } + } + + done(); + }); + }); + + it('should return a user\'s data if queried with type "user"', function (done) { + Flags.getTarget('user', 1, 1, function (err, data) { + assert.ifError(err); + var compare = { + uid: 1, + username: 'testUser', + email: 'b@c.com', + }; + + for (var key in compare) { + if (compare.hasOwnProperty(key)) { + assert.ok(data[key]); + assert.equal(data[key], compare[key]); + } + } + + done(); + }); + }); + + it('should return a plain object with no properties if the target no longer exists', function (done) { + Flags.getTarget('user', 15, 1, function (err, data) { + assert.ifError(err); + assert.strictEqual(0, Object.keys(data).length); + done(); + }); + }); + }); + + describe('.validate()', function () { + it('should error out if type is post and post is deleted', function (done) { + Posts.delete(1, 1, function (err) { + if (err) { + throw err; + } + + Flags.validate({ + type: 'post', + id: 1, + uid: 1, + }, function (err) { + assert.ok(err); + assert.strictEqual('[[error:post-deleted]]', err.message); + Posts.restore(1, 1, done); + }); + }); + }); + + it('should not pass validation if flag threshold is set and user rep does not meet it', function (done) { + Meta.configs.set('privileges:flag', '50', function (err) { + assert.ifError(err); + + Flags.validate({ + type: 'post', + id: 1, + uid: 3, + }, function (err) { + assert.ok(err); + assert.strictEqual('[[error:not-enough-reputation-to-flag]]', err.message); + Meta.configs.set('privileges:flag', 0, done); + }); + }); + }); + }); + + describe('.appendNote()', function () { + it('should add a note to a flag', function (done) { + Flags.appendNote(1, 1, 'this is my note', function (err) { + assert.ifError(err); + + db.getSortedSetRange('flag:1:notes', 0, -1, function (err, notes) { + if (err) { + throw err; + } + + assert.strictEqual('[1,"this is my note"]', notes[0]); + done(); + }); + }); + }); + + it('should be a JSON string', function (done) { + db.getSortedSetRange('flag:1:notes', 0, -1, function (err, notes) { + if (err) { + throw err; + } + + try { + JSON.parse(notes[0]); + } catch (e) { + assert.ifError(e); + } + + done(); + }); + }); + }); + + describe('.getNotes()', function () { + before(function (done) { + // Add a second note + Flags.appendNote(1, 1, 'this is the second note', done); + }); + + it('return should match a predefined spec', function (done) { + Flags.getNotes(1, function (err, notes) { + assert.ifError(err); + var compare = { + uid: 1, + content: 'this is my note', + }; + + var data = notes[1]; + for (var key in compare) { + if (compare.hasOwnProperty(key)) { + assert.ok(data[key]); + assert.strictEqual(data[key], compare[key]); + } + } + + done(); + }); + }); + + it('should retrieve a list of notes, from newest to oldest', function (done) { + Flags.getNotes(1, function (err, notes) { + assert.ifError(err); + assert(notes[0].datetime > notes[1].datetime); + assert.strictEqual('this is the second note', notes[0].content); + done(); + }); + }); + }); + + describe('.appendHistory()', function () { + var entries; + before(function (done) { + db.sortedSetCard('flag:1:history', function (err, count) { + entries = count; + done(err); + }); + }); + + it('should add a new entry into a flag\'s history', function (done) { + Flags.appendHistory(1, 1, { + state: 'rejected', + }, function (err) { + assert.ifError(err); + + Flags.getHistory(1, function (err, history) { + if (err) { + throw err; + } + + assert.strictEqual(entries + 1, history.length); + done(); + }); + }); + }); + }); + + describe('.getHistory()', function () { + it('should retrieve a flag\'s history', function (done) { + Flags.getHistory(1, function (err, history) { + assert.ifError(err); + assert.strictEqual(history[0].fields.state, '[[flags:state-rejected]]'); + done(); + }); + }); + }); + + describe('(websockets)', function () { + var SocketFlags = require('../src/socket.io/flags.js'); + var tid; + var pid; + var flag; + + before(function (done) { + Topics.post({ + cid: 1, + uid: 1, + title: 'Another topic', + content: 'This is flaggable content', + }, function (err, topic) { + tid = topic.postData.tid; + pid = topic.postData.pid; + + done(err); + }); + }); + + describe('.create()', function () { + it('should create a flag with no errors', function (done) { + SocketFlags.create({ uid: 2 }, { + type: 'post', + id: pid, + reason: 'foobar', + }, function (err, flagObj) { + flag = flagObj; + assert.ifError(err); + + Flags.exists('post', pid, 1, function (err, exists) { + assert.ifError(err); + assert(true); + done(); + }); + }); + }); + }); + + describe('.update()', function () { + it('should update a flag\'s properties', function (done) { + SocketFlags.update({ uid: 2 }, { + flagId: 2, + data: [{ + name: 'state', + value: 'wip', + }], + }, function (err, history) { + assert.ifError(err); + assert(Array.isArray(history)); + assert(history[0].fields.hasOwnProperty('state')); + assert.strictEqual('[[flags:state-wip]]', history[0].fields.state); + done(); + }); + }); + }); + + describe('.appendNote()', function () { + it('should append a note to the flag', function (done) { + SocketFlags.appendNote({ uid: 2 }, { + flagId: 2, + note: 'lorem ipsum dolor sit amet', + }, function (err, data) { + assert.ifError(err); + assert(data.hasOwnProperty('notes')); + assert(Array.isArray(data.notes)); + assert.strictEqual('lorem ipsum dolor sit amet', data.notes[0].content); + assert.strictEqual(2, data.notes[0].uid); + + assert(data.hasOwnProperty('history')); + assert(Array.isArray(data.history)); + assert.strictEqual(1, Object.keys(data.history[0].fields).length); + assert(data.history[0].fields.hasOwnProperty('notes')); + done(); + }); + }); + }); + }); + + after(function (done) { + db.emptydb(done); + }); +}); diff --git a/test/locale-detect.js b/test/locale-detect.js new file mode 100644 index 0000000000..1bace9fd75 --- /dev/null +++ b/test/locale-detect.js @@ -0,0 +1,42 @@ +'use strict'; + +var assert = require('assert'); +var nconf = require('nconf'); +var request = require('request'); + +var db = require('./mocks/databasemock'); +var meta = require('../src/meta'); + +describe('Language detection', function () { + it('should detect the language for a guest', function (done) { + meta.config.autoDetectLang = 1; + request(nconf.get('url') + '/api/config', { + headers: { + 'Accept-Language': 'de-DE,de;q=0.5', + }, + }, function (err, res, body) { + assert.ifError(err); + assert.ok(body); + + assert.strictEqual(JSON.parse(body).userLang, 'de'); + done(); + }); + }); + + it('should do nothing when disabled', function (done) { + meta.configs.set('autoDetectLang', 0, function (err) { + assert.ifError(err); + request(nconf.get('url') + '/api/config', { + headers: { + 'Accept-Language': 'de-DE,de;q=0.5', + }, + }, function (err, res, body) { + assert.ifError(err); + assert.ok(body); + + assert.strictEqual(JSON.parse(body).userLang, 'en-GB'); + done(); + }); + }); + }); +}); diff --git a/test/messaging.js b/test/messaging.js index 4fbaad717f..436fd78a87 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -187,6 +187,17 @@ describe('Messaging Library', function () { done(); }); }); + + it('should return true if user is dnd', function (done) { + db.setObjectField('user:' + herpUid, 'status', 'dnd', function (err) { + assert.ifError(err); + socketModules.chats.isDnD({ uid: fooUid }, herpUid, function (err, isDnD) { + assert.ifError(err); + assert(isDnD); + done(); + }); + }); + }); }); describe('edit/delete', function () { @@ -303,7 +314,6 @@ describe('Messaging Library', function () { }); }); - after(function (done) { db.emptydb(done); }); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 412a255dc6..4432f944f1 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -146,7 +146,7 @@ var sockets = require('../../src/socket.io'); sockets.init(webserver.server); - require('../../src/notifications').init(); + require('../../src/notifications').startJobs(); require('../../src/user').startJobs(); webserver.listen(next); diff --git a/test/notifications.js b/test/notifications.js index f21d46cf32..d65b4c0bb5 100644 --- a/test/notifications.js +++ b/test/notifications.js @@ -232,31 +232,6 @@ describe('Notifications', function () { }); }); - it('should error with invalid data', function (done) { - socketNotifications.loadMore({ uid: uid }, { after: 'test' }, function (err) { - assert.equal(err.message, '[[error:invalid-data]]'); - done(); - }); - }); - - it('should error if not logged in', function (done) { - socketNotifications.loadMore({ uid: 0 }, { after: 10 }, function (err) { - assert.equal(err.message, '[[error:no-privileges]]'); - done(); - }); - }); - - it('should load more notifications', function (done) { - socketNotifications.loadMore({ uid: uid }, { after: 0 }, function (err, data) { - assert.ifError(err); - assert.equal(data.notifications[0].bodyShort, 'bodyShort'); - assert.equal(data.notifications[0].nid, 'notification_id'); - assert.equal(data.notifications[0].path, '/notification/path'); - done(); - }); - }); - - it('should error if not logged in', function (done) { socketNotifications.deleteAll({ uid: 0 }, null, function (err) { assert.equal(err.message, '[[error:no-privileges]]'); diff --git a/test/posts.js b/test/posts.js index 49a4783872..e3a0ff1294 100644 --- a/test/posts.js +++ b/test/posts.js @@ -274,16 +274,16 @@ describe('Post\'s', function () { }); }); - it('should purge posts and delete topic', function (done) { + it('should purge posts and purge topic', function (done) { createTopicWithReply(function (topicPostData, replyData) { socketPosts.purgePosts({ uid: voterUid }, { pids: [replyData.pid, topicPostData.postData.pid], tid: topicPostData.topicData.tid }, function (err) { assert.ifError(err); posts.exists('post:' + replyData.pid, function (err, exists) { assert.ifError(err); assert.equal(exists, false); - topics.getTopicField(topicPostData.topicData.tid, 'deleted', function (err, deleted) { + topics.exists(topicPostData.topicData.tid, function (err, exists) { assert.ifError(err); - assert.equal(parseInt(deleted, 10), 1); + assert(!exists); done(); }); }); @@ -496,215 +496,6 @@ describe('Post\'s', function () { }); }); - describe('flagging a post', function () { - var meta = require('../src/meta'); - it('should fail to flag a post due to low reputation', function (done) { - meta.config['privileges:flag'] = 10; - flagPost(function (err) { - assert.equal(err.message, '[[error:not-enough-reputation-to-flag]]'); - done(); - }); - }); - - it('should flag a post', function (done) { - meta.config['privileges:flag'] = -1; - flagPost(function (err) { - assert.ifError(err); - done(); - }); - }); - - it('should return nothing without a uid or a reason', function (done) { - socketPosts.flag({ uid: 0 }, { pid: postData.pid, reason: 'reason' }, function (err) { - assert.equal(err.message, '[[error:not-logged-in]]'); - socketPosts.flag({ uid: voteeUid }, {}, function (err) { - assert.equal(err.message, '[[error:invalid-data]]'); - done(); - }); - }); - }); - - it('should return an error without an existing post', function (done) { - socketPosts.flag({ uid: voteeUid }, { pid: 12312312, reason: 'reason' }, function (err) { - assert.equal(err.message, '[[error:no-post]]'); - done(); - }); - }); - - it('should return an error if the flag already exists', function (done) { - socketPosts.flag({ uid: voteeUid }, { pid: postData.pid, reason: 'reason' }, function (err) { - assert.equal(err.message, '[[error:already-flagged]]'); - done(); - }); - }); - }); - - function flagPost(next) { - socketPosts.flag({ uid: voteeUid }, { pid: postData.pid, reason: 'reason' }, next); - } - - describe('get flag data', function () { - it('should see the flagged post', function (done) { - posts.isFlaggedByUser(postData.pid, voteeUid, function (err, hasFlagged) { - assert.ifError(err); - assert(hasFlagged); - done(); - }); - }); - - it('should return the flagged post data', function (done) { - posts.getFlags('posts:flagged', cid, voteeUid, 0, -1, function (err, flagData) { - assert.ifError(err); - assert(flagData.posts); - assert(flagData.count); - assert.equal(flagData.count, 1); - assert.equal(flagData.posts.length, 1); - assert(flagData.posts[0].flagReasons); - assert.equal(flagData.posts[0].flagReasons.length, 1); - assert.strictEqual(flagData.posts[0].flagReasons[0].reason, 'reason'); - assert(flagData.posts[0].flagData); - assert.strictEqual(flagData.posts[0].flagData.state, 'open'); - done(); - }); - }); - }); - - describe('updating a flag', function () { - it('should update a flag', function (done) { - async.waterfall([ - function (next) { - socketPosts.updateFlag({ uid: globalModUid }, { - pid: postData.pid, - data: [ - { name: 'assignee', value: `${globalModUid}` }, - { name: 'notes', value: 'notes' }, - ], - }, function (err) { - assert.ifError(err); - posts.getFlags('posts:flagged', cid, globalModUid, 0, -1, function (err, flagData) { - assert.ifError(err); - assert(flagData.posts); - assert.equal(flagData.posts.length, 1); - assert.deepEqual({ - assignee: flagData.posts[0].flagData.assignee, - notes: flagData.posts[0].flagData.notes, - state: flagData.posts[0].flagData.state, - labelClass: flagData.posts[0].flagData.labelClass, - }, { - assignee: `${globalModUid}`, - notes: 'notes', - state: 'open', - labelClass: 'info', - }); - next(); - }); - }); - }, function (next) { - posts.updateFlagData(globalModUid, postData.pid, { - state: 'rejected', - }, function (err) { - assert.ifError(err); - posts.getFlags('posts:flagged', cid, globalModUid, 0, -1, function (err, flagData) { - assert.ifError(err); - assert(flagData.posts); - assert.equal(flagData.posts.length, 1); - assert.deepEqual({ - state: flagData.posts[0].flagData.state, - labelClass: flagData.posts[0].flagData.labelClass, - }, { - state: 'rejected', - labelClass: 'danger', - }); - next(); - }); - }); - }, function (next) { - posts.updateFlagData(globalModUid, postData.pid, { - state: 'wip', - }, function (err) { - assert.ifError(err); - posts.getFlags('posts:flagged', cid, globalModUid, 0, -1, function (err, flagData) { - assert.ifError(err); - assert(flagData.posts); - assert.equal(flagData.posts.length, 1); - assert.deepEqual({ - state: flagData.posts[0].flagData.state, - labelClass: flagData.posts[0].flagData.labelClass, - }, { - state: 'wip', - labelClass: 'warning', - }); - next(); - }); - }); - }, function (next) { - posts.updateFlagData(globalModUid, postData.pid, { - state: 'resolved', - }, function (err) { - assert.ifError(err); - posts.getFlags('posts:flagged', cid, globalModUid, 0, -1, function (err, flagData) { - assert.ifError(err); - assert(flagData.posts); - assert.equal(flagData.posts.length, 1); - assert.deepEqual({ - state: flagData.posts[0].flagData.state, - labelClass: flagData.posts[0].flagData.labelClass, - }, { - state: 'resolved', - labelClass: 'success', - }); - next(); - }); - }); - }, - ], done); - }); - }); - - describe('dismissing a flag', function () { - it('should dismiss a flag', function (done) { - socketPosts.dismissFlag({ uid: globalModUid }, postData.pid, function (err) { - assert.ifError(err); - posts.isFlaggedByUser(postData.pid, voteeUid, function (err, hasFlagged) { - assert.ifError(err); - assert(!hasFlagged); - flagPost(function (err) { - assert.ifError(err); - done(); - }); - }); - }); - }); - - it('should dismiss all of a user\'s flags', function (done) { - posts.dismissUserFlags(voteeUid, function (err) { - assert.ifError(err); - posts.isFlaggedByUser(postData.pid, voteeUid, function (err, hasFlagged) { - assert.ifError(err); - assert(!hasFlagged); - flagPost(function (err) { - assert.ifError(err); - done(); - }); - }); - }); - }); - - it('should dismiss all flags', function (done) { - socketPosts.dismissAllFlags({ uid: globalModUid }, {}, function (err) { - assert.ifError(err); - posts.isFlaggedByUser(postData.pid, voteeUid, function (err, hasFlagged) { - assert.ifError(err); - assert(!hasFlagged); - flagPost(function (err) { - assert.ifError(err); - done(); - }); - }); - }); - }); - }); - describe('getPostSummaryByPids', function () { it('should return empty array for empty pids', function (done) { posts.getPostSummaryByPids([], 0, {}, function (err, data) { diff --git a/test/socket.io.js b/test/socket.io.js index 497f0ed922..fc24d24e30 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -227,14 +227,6 @@ describe('socket.io', function () { }); }); - it('should reset flags', function (done) { - socketAdmin.user.resetFlags({ uid: adminUid }, [regularUid], function (err) { - assert.ifError(err); - done(); - }); - }); - - describe('validation emails', function () { var meta = require('../src/meta'); diff --git a/test/topics.js b/test/topics.js index 2222395833..6782f1041d 100644 --- a/test/topics.js +++ b/test/topics.js @@ -526,7 +526,7 @@ describe('Topic\'s', function () { topics.ignore(newTid, uid, done); }, function (done) { - topics.getUnreadTopics(0, uid, 0, -1, '', done); + topics.getUnreadTopics({ cid: 0, uid: uid, start: 0, stop: -1, filter: '' }, done); }, function (results, done) { var topics = results.topics; @@ -570,7 +570,7 @@ describe('Topic\'s', function () { topics.follow(newTid, uid, done); }, function (done) { - topics.getUnreadTopics(0, uid, 0, -1, '', done); + topics.getUnreadTopics({ cid: 0, uid: uid, start: 0, stop: -1, filter: '' }, done); }, function (results, done) { var topics = results.topics; @@ -590,7 +590,7 @@ describe('Topic\'s', function () { topics.follow(newTid, uid, done); }, function (done) { - topics.getUnreadTopics(0, uid, 0, -1, '', done); + topics.getUnreadTopics({ cid: 0, uid: uid, start: 0, stop: -1, filter: '' }, done); }, function (results, done) { var topics = results.topics; @@ -902,7 +902,7 @@ describe('Topic\'s', function () { }); it('should infinite load topic posts', function (done) { - socketTopics.loadMore({ uid: adminUid }, { tid: tid, after: 0 }, function (err, data) { + socketTopics.loadMore({ uid: adminUid }, { tid: tid, after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data.mainPost); assert(data.posts); @@ -921,7 +921,7 @@ describe('Topic\'s', function () { it('should load more unread topics', function (done) { socketTopics.markUnread({ uid: adminUid }, tid, function (err) { assert.ifError(err); - socketTopics.loadMoreUnreadTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0 }, function (err, data) { + socketTopics.loadMoreUnreadTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics)); @@ -939,7 +939,7 @@ describe('Topic\'s', function () { it('should load more recent topics', function (done) { - socketTopics.loadMoreRecentTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0 }, function (err, data) { + socketTopics.loadMoreRecentTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics)); @@ -955,7 +955,7 @@ describe('Topic\'s', function () { }); it('should load more from custom set', function (done) { - socketTopics.loadMoreFromSet({ uid: adminUid }, { set: 'uid:' + adminUid + ':topics', after: 0 }, function (err, data) { + socketTopics.loadMoreFromSet({ uid: adminUid }, { set: 'uid:' + adminUid + ':topics', after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics)); diff --git a/test/translator.js b/test/translator.js index c6cac36ecc..4f5ce04a41 100644 --- a/test/translator.js +++ b/test/translator.js @@ -4,6 +4,7 @@ var assert = require('assert'); var shim = require('../public/src/modules/translator.js'); var Translator = shim.Translator; +var db = require('./mocks/databasemock'); require('../src/languages').init(function () {}); @@ -118,10 +119,20 @@ describe('new Translator(language)', function () { it('should properly escape and ignore % and \\, in arguments', function () { var translator = Translator.create('en-GB'); - var title = 'Test 1\\, 2\\, 3 % salmon'; + var title = 'Test 1\\, 2\\, 3 %2 salmon'; var key = '[[topic:composer.replying_to, ' + title + ']]'; return translator.translate(key).then(function (translated) { - assert.strictEqual(translated, 'Replying to Test 1, 2, 3 % salmon'); + assert.strictEqual(translated, 'Replying to Test 1, 2, 3 %2 salmon'); + }); + }); + + it('should not escape regular %', function () { + var translator = Translator.create('en-GB'); + + var title = '3 % salmon'; + var key = '[[topic:composer.replying_to, ' + title + ']]'; + return translator.translate(key).then(function (translated) { + assert.strictEqual(translated, 'Replying to 3 % salmon'); }); }); diff --git a/test/uploads.js b/test/uploads.js index 0daf0407b9..a4fe95c5df 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -104,7 +104,10 @@ describe('Upload Controllers', function () { it('should upload a file to a post', function (done) { meta.config.allowFileUploads = 1; + var oldValue = meta.config.allowedFileExtensions; + meta.config.allowedFileExtensions = 'png,jpg,bmp,html'; helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/503.html'), { cid: cid }, jar, csrf_token, function (err, res, body) { + meta.config.allowedFileExtensions = oldValue; assert.ifError(err); assert.equal(res.statusCode, 200); assert(Array.isArray(body)); diff --git a/test/user.js b/test/user.js index 08d3ba42d4..943c348872 100644 --- a/test/user.js +++ b/test/user.js @@ -945,7 +945,11 @@ describe('User', function () { }; socketUser.saveSettings({ uid: testUid }, data, function (err) { assert.ifError(err); - done(); + User.getSettings(testUid, function (err, data) { + assert.ifError(err); + assert.equal(data.usePagination, true); + done(); + }); }); }); @@ -956,9 +960,14 @@ describe('User', function () { assert.ifError(err); socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: 'this is a test user' }, function (err) { assert.ifError(err); - User.getUserField(testUid, 'moderationNote', function (err, note) { + db.getSortedSetRevRange('uid:' + testUid + ':moderation:notes', 0, 0, function (err, notes) { assert.ifError(err); - assert.equal(note, 'this is a test user'); + notes = notes.map(function (noteData) { + return JSON.parse(noteData); + }); + assert.equal(notes[0].note, 'this is a test user'); + assert.equal(notes[0].uid, adminUid); + assert(notes[0].timestamp); done(); }); });