diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6d5e4f1201..a26d415c49 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -38,11 +38,9 @@ There is a chance that the issue you are experiencing may have already been fixe You can find the NodeBB version number in the Admin Control Panel (ACP), as well as the first line output to the shell when running NodeBB ``` plaintext -info: NodeBB v0.5.2-dev Copyright (C) 2013-2014 NodeBB Inc. -info: This program comes with ABSOLUTELY NO WARRANTY. -info: This is free software, and you are welcome to redistribute it under certain conditions. -info: -info: Time: Tue Oct 07 2014 20:25:20 GMT-0400 (EDT) +3/4 12:38:57 [10752] - info: NodeBB v1.4.5 Copyright (C) 2013-2017 NodeBB Inc. +3/4 12:38:57 [10752] - info: This program comes with ABSOLUTELY NO WARRANTY. +3/4 12:38:57 [10752] - info: This is free software, and you are welcome to redistribute it under certain conditions. ``` If you are running NodeBB via git, it is also helpful to let the maintainers know what commit hash you are on. To find the commit hash, execute the following command: diff --git a/app.js b/app.js index 3b5efcc00f..9dcad7a408 100644 --- a/app.js +++ b/app.js @@ -19,6 +19,12 @@ 'use strict'; +if (require.main !== module) { + require.main.require = function (path) { + return require(path); + }; +} + var nconf = require('nconf'); nconf.argv().env('__'); diff --git a/loader.js b/loader.js index 654d77fb23..214f785eb9 100644 --- a/loader.js +++ b/loader.js @@ -7,6 +7,7 @@ var path = require('path'); var fork = require('child_process').fork; var async = require('async'); var logrotate = require('logrotate-stream'); + var file = require('./src/file'); var pkg = require('./package.json'); @@ -23,6 +24,7 @@ var workers = []; var Loader = { timesStarted: 0, }; +var appPath = path.join(__dirname, 'app.js'); Loader.init = function (callback) { if (silent) { @@ -114,7 +116,7 @@ function forkWorker(index, isPrimary) { process.env.isCluster = ports.length > 1; process.env.port = ports[index]; - var worker = fork('app.js', args, { + var worker = fork(appPath, args, { silent: silent, env: process.env, }); diff --git a/nodebb b/nodebb index f0fac9d0cf..82c7c2d8a6 100755 --- a/nodebb +++ b/nodebb @@ -2,16 +2,25 @@ 'use strict'; +var cproc; +var args; +var fs; +var path; +var request; +var semver; +var prompt; +var async; + try { require('colors'); - var cproc = require('child_process'); - var args = require('minimist')(process.argv.slice(2)); - var fs = require('fs'); - var path = require('path'); - var request = require('request'); - var semver = require('semver'); - var prompt = require('prompt'); - var async = require('async'); + cproc = require('child_process'); + args = require('minimist')(process.argv.slice(2)); + fs = require('fs'); + path = require('path'); + request = require('request'); + semver = require('semver'); + prompt = require('prompt'); + async = require('async'); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { process.stdout.write('NodeBB could not be started because it\'s dependencies have not been installed.\n'); @@ -23,12 +32,15 @@ try { } } +var loaderPath = path.join(__dirname, 'loader.js'); +var appPath = path.join(__dirname, 'app.js'); + if (args.dev) { process.env.NODE_ENV = 'development'; } function getRunningPid(callback) { - fs.readFile(__dirname + '/pidfile', { + fs.readFile(path.join(__dirname, 'pidfile'), { encoding: 'utf-8', }, function (err, pid) { if (err) { @@ -58,7 +70,7 @@ function getCurrentVersion(callback) { }); } function fork(args) { - return cproc.fork('app.js', args, { + return cproc.fork(appPath, args, { cwd: __dirname, silent: false, }); @@ -72,7 +84,7 @@ function getInstalledPlugins(callback) { return callback(err); } - var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w\-]+$/; + var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/; var moduleName; var isGitRepo; @@ -170,21 +182,21 @@ function checkPlugins(standalone, callback) { body = [body]; } - var current, - suggested, - upgradable = body.map(function (suggestObj) { - current = payload.plugins[suggestObj.package]; - suggested = suggestObj.version; - - if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) { - return { - name: suggestObj.package, - current: current, - suggested: suggested, - }; - } - return null; - }).filter(Boolean); + var current; + var suggested; + var upgradable = body.map(function (suggestObj) { + current = payload.plugins[suggestObj.package]; + suggested = suggestObj.version; + + if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) { + return { + name: suggestObj.package, + current: current, + suggested: suggested, + }; + } + return null; + }).filter(Boolean); next(null, upgradable); }); @@ -200,7 +212,7 @@ function upgradePlugins(callback) { checkPlugins(standalone, function (err, found) { if (err) { - process.stdout.write('\Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset); + process.stdout.write('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset); return callback(err); } @@ -280,7 +292,7 @@ var commands = { process.stdout.write(' "' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'.reset); // Spawn a new NodeBB process - cproc.fork(__dirname + '/loader.js', { + cproc.fork(loaderPath, { env: process.env, }); }, @@ -334,7 +346,7 @@ var commands = { process.stdout.write('\n\n'.reset); // Spawn a new NodeBB process - cproc.fork(__dirname + '/loader.js', { + cproc.fork(loaderPath, { env: process.env, }); cproc.spawn('tail', ['-F', './logs/output.log'], { @@ -348,7 +360,7 @@ var commands = { usage: 'Usage: ' + './nodebb dev'.yellow, handler: function () { process.env.NODE_ENV = 'development'; - cproc.fork(__dirname + '/loader.js', ['--no-daemon', '--no-silent'], { + cproc.fork(loaderPath, ['--no-daemon', '--no-silent'], { env: process.env, }); }, diff --git a/package.json b/package.json index 324e2a6ee9..4d55cb0d0a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "main": "app.js", "scripts": { "start": "node loader.js", - "lint": "eslint --cache .", + "lint": "eslint --cache ./nodebb .", "pretest": "npm run lint", "test": "istanbul cover node_modules/mocha/bin/_mocha -- -R dot", "coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" @@ -95,7 +95,8 @@ "validator": "^6.1.0", "winston": "^2.1.0", "xml": "^1.0.1", - "xregexp": "~3.1.0" + "xregexp": "~3.1.0", + "zxcvbn": "^4.4.2" }, "devDependencies": { "coveralls": "^2.11.14", diff --git a/public/language/en-GB/admin/settings/user.json b/public/language/en-GB/admin/settings/user.json index bdabb075e9..b8f51c9288 100644 --- a/public/language/en-GB/admin/settings/user.json +++ b/public/language/en-GB/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-GB/user.json b/public/language/en-GB/user.json index 7b79af9ce7..2f5e588881 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -63,6 +63,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", diff --git a/public/language/hu/global.json b/public/language/hu/global.json index ea60f7dd9b..c05367f938 100644 --- a/public/language/hu/global.json +++ b/public/language/hu/global.json @@ -61,7 +61,7 @@ "reputation": "Hírnév", "read_more": "tovább olvas", "more": "Több", - "posted_ago_by_guest": "Vendég hozzászólás %1", + "posted_ago_by_guest": "%1 vendég hozzászólás", "posted_ago_by": "%2 hozzászólás %1", "posted_ago": "%1 hozzászólás", "posted_in": "hozzászólt itt: %1", @@ -103,5 +103,5 @@ "cookies.message": "A weboldal sütiket használ, a legjobb weboldalas élmény érdekében.", "cookies.accept": "Értem!", "cookies.learn_more": "Tudnivalók", - "edited": "Edited" + "edited": "Szerkesztett" } \ No newline at end of file diff --git a/public/language/hu/recent.json b/public/language/hu/recent.json index bddd57893f..95004a40a6 100644 --- a/public/language/hu/recent.json +++ b/public/language/hu/recent.json @@ -4,7 +4,7 @@ "week": "Hét", "month": "Hónap", "year": "Év", - "alltime": "Minden idők", + "alltime": "Bármikor", "no_recent_topics": "Nincs friss témakör.", "no_popular_topics": "Nincs népszerű témakör.", "there-is-a-new-topic": "Van egy új témakör.", diff --git a/public/language/nl/global.json b/public/language/nl/global.json index 73eb607f24..f18dccae8d 100644 --- a/public/language/nl/global.json +++ b/public/language/nl/global.json @@ -103,5 +103,5 @@ "cookies.message": "Deze website gebruikt cookies om je ervan te verzekeren dat je de beste ervaring krijgt tijdens het gebruik van onze website.", "cookies.accept": "Begrepen", "cookies.learn_more": "Meer", - "edited": "Edited" + "edited": "Bewerkt" } \ No newline at end of file diff --git a/public/language/nl/groups.json b/public/language/nl/groups.json index 912765ec55..0cf86d03a7 100644 --- a/public/language/nl/groups.json +++ b/public/language/nl/groups.json @@ -27,7 +27,7 @@ "details.disableJoinRequests": "Groepsverzoeken uitschakelen", "details.grant": "Toekennen/herroepen van eigendom", "details.kick": "Kick", - "details.kick_confirm": "Are you sure you want to remove this member from the group?", + "details.kick_confirm": "Weet u zeker dat u de gebruiker wilt verwijderen uit de groep?", "details.owner_options": "Groepsadministratie", "details.group_name": "Groepsnaam", "details.member_count": "Ledentelling", @@ -54,5 +54,5 @@ "upload-group-cover": "Upload groepscover", "bulk-invite-instructions": "Vul een lijst is met gebruikersnamen gescheiden met komma's om deze uit te nodigen voor deze groep", "bulk-invite": "Massa uitnodiging", - "remove_group_cover_confirm": "Are you sure you want to remove the cover picture?" + "remove_group_cover_confirm": "Weet u zeker dat u de cover foto wilt verwijderen?" } \ No newline at end of file diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json index 34c8925ae7..59d0396464 100644 --- a/public/language/nl/modules.json +++ b/public/language/nl/modules.json @@ -13,7 +13,7 @@ "chat.contacts": "Contacten", "chat.message-history": "Berichtengeschiedenis", "chat.pop-out": "Chatvenster opbrengen bij chat", - "chat.minimize": "Minimize", + "chat.minimize": "Verkleinen", "chat.maximize": "Maximaliseren", "chat.seven_days": "7 dagen", "chat.thirty_days": "30 dagen", diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json index a15226841a..29b4234fb7 100644 --- a/public/language/nl/notifications.json +++ b/public/language/nl/notifications.json @@ -10,14 +10,14 @@ "return_to": "Terug naar %1", "new_notification": "Nieuwe notificatie", "you_have_unread_notifications": "Je hebt nieuwe notificaties.", - "all": "All", - "topics": "Topics", - "replies": "Replies", + "all": "Alles", + "topics": "Onderwerpen", + "replies": "Antwoorden", "chat": "Chats", - "follows": "Follows", + "follows": "Volgt", "upvote": "Upvotes", - "new-flags": "New Flags", - "my-flags": "Flags assigned to me", + "new-flags": "Nieuwe markeringen", + "my-flags": "Markeringen toegewezen aan mij", "bans": "Bans", "new_message_from": "Nieuw bericht van %1", "upvoted_your_post_in": "%1 heeft voor een bericht gestemd in %2.", @@ -28,9 +28,9 @@ "user_flagged_post_in": "%1 rapporteerde een bericht in %2", "user_flagged_post_in_dual": "%1 en %2 rapporteerde een bericht in %3", "user_flagged_post_in_multiple": "%1 en %2 andere rapporteede een bericht in %3", - "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_flagged_user": "%1 markeerde een gebruikersprofiel (%2)", + "user_flagged_user_dual": "%1 en %2 markeerden een gebruikersprofiel (%3)", + "user_flagged_user_multiple": "%1 en %2 anderen markeerde een gebruikersprofiel (%3)", "user_posted_to": "%1 heeft een reactie geplaatst in %2", "user_posted_to_dual": "%1 en %2 hebben een reactie geplaatst in: %3", "user_posted_to_multiple": "%1 en %2 hebben een reactie geplaatst in: %3", @@ -40,7 +40,7 @@ "user_started_following_you_multiple": "%1 en %2 andere volgen jou nu.", "new_register": "%1 heeft een registratie verzoek aangevraagd.", "new_register_multiple": "Er is/zijn %1 registratieverzoek(en) die wacht(en) op goedkeuring.", - "flag_assigned_to_you": "Flag %1 has been assigned to you", + "flag_assigned_to_you": "Flag %1 is aan u toegewezen", "email-confirmed": "E-mailadres bevestigd", "email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Je account is nu volledig geactiveerd.", "email-confirm-error-message": "Er was een probleem met het bevestigen van dit e-mailadres. Misschien is de code niet goed ingevoerd of was de beschikbare tijd inmiddels verstreken.", diff --git a/public/language/nl/pages.json b/public/language/nl/pages.json index 816f62be22..64441b8b54 100644 --- a/public/language/nl/pages.json +++ b/public/language/nl/pages.json @@ -6,7 +6,7 @@ "popular-month": "De populaire onderwerpen van deze maand", "popular-alltime": "De populaire onderwerpen", "recent": "Recente onderwerpen", - "flagged-content": "Flagged Content", + "flagged-content": "Gemarkeerde content", "ip-blacklist": "IP zwarte lijst", "users/online": "Online Gebruikers", "users/latest": "Meest recente gebruikers", @@ -27,7 +27,7 @@ "group": "%1's groep", "chats": "Chats", "chat": "Chatten met %1", - "flags": "Flags", + "flags": "Markeringen", "flag-details": "Flag %1 Details", "account/edit": "\"%1\" aanpassen", "account/edit/password": "Wachtwoord van \"%1\" aanpassen", diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json index 578b8c2ffc..4efbae1f80 100644 --- a/public/language/tr/notifications.json +++ b/public/language/tr/notifications.json @@ -10,17 +10,17 @@ "return_to": "Geri dön.", "new_notification": "Yeni bildirim", "you_have_unread_notifications": "Okunmamış bildirimleriniz var.", - "all": "All", - "topics": "Topics", - "replies": "Replies", - "chat": "Chats", - "follows": "Follows", + "all": "Hepsi", + "topics": "Başlıklar", + "replies": "Yanıtlar", + "chat": "Sohbetler", + "follows": "Takip ediyor", "upvote": "Upvotes", - "new-flags": "New Flags", + "new-flags": "Yeni Bayrak", "my-flags": "Flags assigned to me", - "bans": "Bans", + "bans": "Yasaklamalar", "new_message_from": "%1 size bir mesaj gönderdi", - "upvoted_your_post_in": "%1 iletinizi beğendi. %2", + "upvoted_your_post_in": "%1 iletinizi beğendi. %2.", "upvoted_your_post_in_dual": "%1 ve %2 %3 içindeki gönderini beğendi.", "upvoted_your_post_in_multiple": "%1 ve %2 iki kişi daha %3 içindeki gönderini beğendi.", "moved_your_post": "%1 senin iletin %2 taşındı", diff --git a/public/language/tr/user.json b/public/language/tr/user.json index 0b3e1b576d..05dcd10d0a 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -131,5 +131,5 @@ "info.email-history": "Email Geçmişi", "info.moderation-note": "Moderasyon Notu", "info.moderation-note.success": "Moderasyon notu kaydedildi", - "info.moderation-note.add": "Add note" + "info.moderation-note.add": "Not ekle" } \ No newline at end of file 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/user.json b/public/language/uk/user.json index c8bd7ca5b2..bad807379e 100644 --- a/public/language/uk/user.json +++ b/public/language/uk/user.json @@ -131,5 +131,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/zh-CN/admin/advanced/database.json b/public/language/zh-CN/admin/advanced/database.json index 0388631265..5519709794 100644 --- a/public/language/zh-CN/admin/advanced/database.json +++ b/public/language/zh-CN/admin/advanced/database.json @@ -26,7 +26,7 @@ "redis.blocked-clients": "阻止的客户端", "redis.used-memory": "已使用内存", "redis.memory-frag-ratio": "内存碎片比率", - "redis.total-connections-recieved": "已接收连接总数", + "redis.total-connections-recieved": "已接收的连接总数", "redis.total-commands-processed": "已执行命令总数", "redis.iops": "每秒实时操作数", "redis.keyspace-hits": "Keyspace 命中", diff --git a/public/language/zh-CN/admin/advanced/events.json b/public/language/zh-CN/admin/advanced/events.json index 3c890a81eb..85d741ff27 100644 --- a/public/language/zh-CN/admin/advanced/events.json +++ b/public/language/zh-CN/admin/advanced/events.json @@ -2,5 +2,5 @@ "events": "事件", "no-events": "暂无事件。", "control-panel": "事件控制面板", - "delete-events": "删除事件" + "delete-events": "清除事件" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/extend/plugins.json b/public/language/zh-CN/admin/extend/plugins.json index 06edee6076..21446a0de9 100644 --- a/public/language/zh-CN/admin/extend/plugins.json +++ b/public/language/zh-CN/admin/extend/plugins.json @@ -1,6 +1,6 @@ { "installed": "已安装", - "active": "生效中", + "active": "激活", "inactive": "未生效", "out-of-date": "已过期", "none-found": "无插件。", diff --git a/public/language/zh-CN/admin/extend/widgets.json b/public/language/zh-CN/admin/extend/widgets.json index 3c3d76eff3..36c987b0f9 100644 --- a/public/language/zh-CN/admin/extend/widgets.json +++ b/public/language/zh-CN/admin/extend/widgets.json @@ -13,7 +13,7 @@ "container.alert": "警报", "alert.confirm-delete": "确认删除此窗口部件?", - "alert.updated": "窗口部件升级", - "alert.update-success": "已成功升级窗口部件" + "alert.updated": "窗口部件更新", + "alert.update-success": "已成功更新窗口部件" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/general/dashboard.json b/public/language/zh-CN/admin/general/dashboard.json index f44b053421..9a17c65e68 100644 --- a/public/language/zh-CN/admin/general/dashboard.json +++ b/public/language/zh-CN/admin/general/dashboard.json @@ -33,7 +33,7 @@ "control-panel": "系统控制", "reload": "重载", "restart": "重启", - "restart-warning": "重新载入或重启 NodeBB 会丢弃数秒内所有的连接。", + "restart-warning": "重载或重启 NodeBB 会丢失数秒内所有的连接。", "maintenance-mode": "维护模式", "maintenance-mode-title": "点击此处设置 NodeBB 的维护模式", "realtime-chart-updates": "实时图表更新", diff --git a/public/language/zh-CN/admin/manage/categories.json b/public/language/zh-CN/admin/manage/categories.json index a881fce346..77aa93ec68 100644 --- a/public/language/zh-CN/admin/manage/categories.json +++ b/public/language/zh-CN/admin/manage/categories.json @@ -12,57 +12,57 @@ "ext-link": "外部链接", "upload-image": "上传图片", "delete-image": "移除", - "category-image": "板块图片", - "parent-category": "父板块", - "optional-parent-category": "(可选)父板块", + "category-image": "版块图片", + "parent-category": "父版块", + "optional-parent-category": "(可选)父版块", "parent-category-none": "(无)", "copy-settings": "复制设置", - "optional-clone-settings": "(可选) 从板块复制设置", - "purge": "删除板块", + "optional-clone-settings": "(可选) 从版块复制设置", + "purge": "删除版块", "enable": "启用", "disable": "禁用", "edit": "编辑", - "select-category": "选择板块", - "set-parent-category": "设置父板块", + "select-category": "选择版块", + "set-parent-category": "设置父版块", - "privileges.description": "您可以在此部分中配置此板块的访问控制权限。 可以根据每个用户或每个组授予权限。 您可以通过在下面的表格中搜索,将新用户添加到此表中。", - "privileges.warning": "注意:权限设置会立即生效。 调整这些设置后,无需保存。", + "privileges.description": "您可以在此部分中配置此版块的访问控制权限。 可以根据每个用户或每个组授予权限。 您可以通过在下面的表格中搜索,将新用户添加到此表中。", + "privileges.warning": "注意:权限设置会立即生效。 调整这些设置后,无需保存。", "privileges.section-viewing": "查看权限", "privileges.section-posting": "发帖权限", "privileges.section-moderation": "审核权限", "privileges.section-user": "用户", "privileges.search-user": "添加用户", - "privileges.no-users": "此类别中没有用户特定的权限。", - "privileges.section-group": "用户组", - "privileges.group-private": "这个用户组是私密的", - "privileges.search-group": "添加用户组", + "privileges.no-users": "此版块中没有用户特定的权限。", + "privileges.section-group": "群组", + "privileges.group-private": "这个群组是私密的", + "privileges.search-group": "添加群组", "privileges.copy-to-children": "复制到子版块", - "privileges.copy-from-category": "从板块复制", - "privileges.inherit": "如果 registered-users 组被授予特定权限,所有其他组都会收到隐式权限,即使它们未被明确定义/检查。 将显示此隐式权限,因为所有用户都是 registered-users 用户组的一部分,因此无需显式授予其他组的权限。", + "privileges.copy-from-category": "从版块复制", + "privileges.inherit": "如果 registered-users 组被授予特定权限,所有其他组都会收到隐式权限,即使它们未被明确定义/检查。 将显示此隐式权限,因为所有用户都是 registered-users 群组的一部分,因此无需显式授予其他组的权限。", - "analytics.back": "返回板块列表", - "analytics.title": "“%1”板块的统计", - "analytics.pageviews-hourly": "图1 – 此板块的每小时页面浏览量", - "analytics.pageviews-daily": "图2 – 此板块的每日页面浏览量", - "analytics.topics-daily": "图3 – 每日在此板块中创建的主题", - "analytics.posts-daily": "图4 – 每日在此板块中每日发布的帖子", + "analytics.back": "返回版块列表", + "analytics.title": "“%1”版块的统计", + "analytics.pageviews-hourly": "图1 – 此版块的每小时页面浏览量", + "analytics.pageviews-daily": "图2 – 此版块的每日页面浏览量", + "analytics.topics-daily": "图3 – 每日在此版块中创建的主题", + "analytics.posts-daily": "图4 – 每日在此版块中每日发布的帖子", "alert.created": "创建", - "alert.create-success": "板块创建成功!", - "alert.none-active": "您没有有效的板块。", - "alert.create": "创建一个板块", - "alert.confirm-moderate": "您确定要将审核权限授予此用户组吗?此群组是公开的,任何用户都可以随意加入。", - "alert.confirm-purge": "

您确定要清除此板块“%1”吗?

警告! 板块将被清除!

清除板块将删除所有主题和帖子,并从数据库中删除板块。 如果您想暂时移除板块,请使用停用板块。", - "alert.purge-success": "板块已删除!", + "alert.create-success": "版块创建成功!", + "alert.none-active": "您没有有效的版块。", + "alert.create": "创建一个版块", + "alert.confirm-moderate": "您确定要将审核权限授予此群组吗?此群组是公开的,任何用户都可以随意加入。", + "alert.confirm-purge": "

您确定要清除此版块“%1”吗?

警告! 版块将被清除!

清除版块将删除所有主题和帖子,并从数据库中删除版块。 如果您想暂时移除版块,请使用停用版块。

", + "alert.purge-success": "版块已删除!", "alert.copy-success": "设置已复制!", - "alert.set-parent-category": "设置父板块", - "alert.updated": "板块已更新", - "alert.updated-success": "板块ID %1 成功更新。", - "alert.upload-image": "上传板块图片", + "alert.set-parent-category": "设置父版块", + "alert.updated": "版块已更新", + "alert.updated-success": "版块ID %1 成功更新。", + "alert.upload-image": "上传版块图片", "alert.find-user": "查找用户", "alert.user-search": "在这里查找用户…", - "alert.find-group": "查找用户组", - "alert.group-search": "在此处搜索用户组..." + "alert.find-group": "查找群组", + "alert.group-search": "在此处搜索群组..." } \ 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 f2b90b98a0..131199f22c 100644 --- a/public/language/zh-CN/admin/manage/groups.json +++ b/public/language/zh-CN/admin/manage/groups.json @@ -1,31 +1,31 @@ { - "name": "用户组名", - "description": "用户组描述", - "system": "系统用户组", + "name": "群组名", + "description": "群组描述", + "system": "系统群组", "edit": "编辑", - "search-placeholder": "索索", - "create": "创建用户组", - "description-placeholder": "一个关于你的用户组的简短描述", + "search-placeholder": "搜索", + "create": "创建群组", + "description-placeholder": "一个关于你的群组的简短描述", "create-button": "创建", - "alerts.create-failure": "哦不!

创建您的用户组时出现问题。 请稍后再试!

", - "alerts.confirm-delete": "确认要删除这个用户组么?", + "alerts.create-failure": "哦不!

创建您的群组时出现问题。 请稍后再试!

", + "alerts.confirm-delete": "确认要删除这个群组么?", "edit.name": "名字", "edit.description": "描述", "edit.user-title": "成员标题", - "edit.icon": "用户组标志", + "edit.icon": "群组标志", "edit.label-color": "群组标签颜色", "edit.show-badge": "显示徽章", - "edit.private-details": "启用此选项后,加入用户组的请求将需要组长审批。", - "edit.private-override": "警告:系统禁用了私有用户组,优先于该选项。", + "edit.private-details": "启用此选项后,加入群组的请求将需要群组所有者审批。", + "edit.private-override": "警告:系统已禁用了私有群组,优先级高于该选项。", "edit.disable-requests": "禁止加入请求", "edit.hidden": "隐藏", - "edit.hidden-details": "启用此选项后,此用户组将不在用户组列表展现,并且用户只能被手动邀请加入", - "edit.add-user": "向此小组添加成员", + "edit.hidden-details": "启用此选项后,此群组将不在群组列表展现,并且用户只能被手动邀请加入", + "edit.add-user": "向此群组添加成员", "edit.add-user-search": "搜索用户", "edit.members": "成员列表", - "control-panel": "小组控制面板", + "control-panel": "群组控制面板", "revert": "重置", "edit.no-users-found": "没有找到用户", diff --git a/public/language/zh-CN/admin/manage/registration.json b/public/language/zh-CN/admin/manage/registration.json index 26ec422c98..aa46fcdb49 100644 --- a/public/language/zh-CN/admin/manage/registration.json +++ b/public/language/zh-CN/admin/manage/registration.json @@ -1,6 +1,6 @@ { - "queue": "队列", - "description": "注册队列里面没有用户。
要开启这项功能,请去设置 → 用户 → 用户注册 并设置注册类型为“管理员批准”。", + "queue": "申请", + "description": "注册申请队列里面还没有用户申请。
要开启这项功能,请去设置 → 用户 → 用户注册 并设置注册类型为“管理员批准”。", "list.name": "姓名", "list.email": "邮件", diff --git a/public/language/zh-CN/admin/manage/users.json b/public/language/zh-CN/admin/manage/users.json index d8570e5977..8201df27c6 100644 --- a/public/language/zh-CN/admin/manage/users.json +++ b/public/language/zh-CN/admin/manage/users.json @@ -30,9 +30,9 @@ "search.username": "通过用户名", "search.username-placeholder": "输入你想找的用户名", "search.email": "通过邮箱", - "search.email-placeholder": "输入你想找的邮箱地址", + "search.email-placeholder": "输入你想查询的邮箱地址", "search.ip": "通过IP地址", - "search.ip-placeholder": "输入你想找的IP", + "search.ip-placeholder": "输入你想查询的IP", "search.not-found": "未找到用户!", "inactive.3-months": "3个月", diff --git a/public/language/zh-CN/admin/menu.json b/public/language/zh-CN/admin/menu.json index 1a6771b20e..37f31c027a 100644 --- a/public/language/zh-CN/admin/menu.json +++ b/public/language/zh-CN/admin/menu.json @@ -11,8 +11,8 @@ "manage/categories": "版块", "manage/tags": "话题", "manage/users": "用户", - "manage/registration": "注册队列", - "manage/groups": "用户组", + "manage/registration": "注册申请", + "manage/groups": "群组", "manage/ip-blacklist": "IP 黑名单", "section-settings": "设置", @@ -20,7 +20,7 @@ "settings/reputation": "声望", "settings/email": "邮件", "settings/user": "用户", - "settings/group": "用户组", + "settings/group": "群组", "settings/guest": "游客", "settings/uploads": "上传", "settings/post": "发帖", diff --git a/public/language/zh-CN/admin/settings/advanced.json b/public/language/zh-CN/admin/settings/advanced.json index 07184f136b..eb9bced36e 100644 --- a/public/language/zh-CN/admin/settings/advanced.json +++ b/public/language/zh-CN/admin/settings/advanced.json @@ -10,7 +10,7 @@ "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", "traffic-management": "流量管理", - "traffic.help": "NodeBB 拥有在高流量情况下自动拒绝请求的模块。 您可以在这里调整这些设置,虽然默认值就很棒。", + "traffic.help": "NodeBB 拥有在高流量情况下自动拒绝请求的模块。尽管默认值就很棒,但您可以在这里调整这些设置。", "traffic.enable": "启用流量管理", "traffic.event-lag": "事件循环滞后阈值(毫秒)", "traffic.event-lag-help": "降低此值会减少页面加载的等待时间,但也会向更多用户显示“过载”消息。(需要重新启动)", diff --git a/public/language/zh-CN/admin/settings/cookies.json b/public/language/zh-CN/admin/settings/cookies.json index a03212d78d..d8b00e8295 100644 --- a/public/language/zh-CN/admin/settings/cookies.json +++ b/public/language/zh-CN/admin/settings/cookies.json @@ -6,6 +6,6 @@ "consent.link-text": "政策链接文本", "consent.blank-localised-default": "留空以便使用 NodeBB 本地默认值", "settings": "设置", - "cookie-domain": "会话 cookie 域名", + "cookie-domain": "Session cookie 域名", "blank-default": "留空以保持默认" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/group.json b/public/language/zh-CN/admin/settings/group.json index d19d417f86..bd1771dd57 100644 --- a/public/language/zh-CN/admin/settings/group.json +++ b/public/language/zh-CN/admin/settings/group.json @@ -1,12 +1,12 @@ { "general": "通用", - "private-groups": "私有用户组", - "private-groups.help": "启用此选项后,加入用户组需要组长审批(默认启用)。", - "private-groups.warning": "注意!如果这个选项未启用并且你有私有用户组,那么你的用户组将变为公共的。", - "allow-creation": "允许创建用户组", - "allow-creation-help": "如果启用,用户就可以创建用户组(默认:不启用)", - "max-name-length": "用户组名字的最大长度", - "cover-image": "用户组封面图片", + "private-groups": "私有群组", + "private-groups.help": "启用此选项后,加入用户组需要群组所有者审批(默认启用)。", + "private-groups.warning": "注意!如果这个选项未启用并且你有私有群组,那么你的群组将变为公共的。", + "allow-creation": "允许创建群组", + "allow-creation-help": "如果启用,用户就可以创建群组(默认:不启用)", + "max-name-length": "群组名字的最大长度", + "cover-image": "群组封面图片", "default-cover": "默认封面图片", "default-cover-help": "为没有上传封面图片的群组添加以逗号分隔的默认封面图片" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/pagination.json b/public/language/zh-CN/admin/settings/pagination.json index ec44dd3661..b4e16d4f7f 100644 --- a/public/language/zh-CN/admin/settings/pagination.json +++ b/public/language/zh-CN/admin/settings/pagination.json @@ -3,7 +3,7 @@ "enable": "在主题和帖子使用分页替代无限滚动浏览。", "topics": "话题分页", "posts-per-page": "每页帖子数", - "categories": "板块分页", + "categories": "版块分页", "topics-per-page": "每页主题数", "initial-num-load": "最初加载未读,最新,热门的话题" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/post.json b/public/language/zh-CN/admin/settings/post.json index a5869c1fc1..857f72b98e 100644 --- a/public/language/zh-CN/admin/settings/post.json +++ b/public/language/zh-CN/admin/settings/post.json @@ -6,21 +6,21 @@ "sorting.most-votes": "最多投票", "sorting.topic-default": "默认主题排序", "restrictions": "发帖限制", - "restrictions.seconds-between": "发帖间隔", - "restrictions.seconds-between-new": "对于新用户的发帖间隔", + "restrictions.seconds-between": "发帖间隔(单位:秒)", + "restrictions.seconds-between-new": "对于新用户的发帖间隔(单位:秒)", "restrictions.rep-threshold": "取消发帖限制所需的声望值", - "restrictions.seconds-defore-new": "见习时间", - "restrictions.seconds-edit-after": "用户在发布后允许编辑帖子的秒数。 (0为禁用) ", - "restrictions.seconds-delete-after": "允许在发布后删除帖子的秒数。 (0为禁用) ", + "restrictions.seconds-defore-new": "见习时间(单位:秒)", + "restrictions.seconds-edit-after": "用户在发布后允许编辑帖子的时间(0为禁用,单位:秒)", + "restrictions.seconds-delete-after": "用户在发布后允许删除帖子的时间(0为禁用,单位:秒)", "restrictions.replies-no-delete": "在用户被禁止删除自己的主题后的回复数。 (0为禁用) ", "restrictions.min-title-length": "最小标题长度", "restrictions.max-title-length": "最大标题长度", "restrictions.min-post-length": "最小帖子长度", "restrictions.max-post-length": "最大帖子长度", - "restrictions.days-until-stale": "主题过期时间", + "restrictions.days-until-stale": "主题过期时间(单位:天)", "restrictions.stale-help": "如果某个主题被视为“过时”,则会向尝试回复该主题的用户显示警告。", "timestamp": "时间戳", - "timestamp.cut-off": "日期截止日期 (天) ", + "timestamp.cut-off": "日期截止日期(单位:天)", "timestamp.cut-off-help": "日期&时间将以相对方式 (例如,“3小时前” / “5天前”) 显示,并且会依照访客语言时区转换。在某一时刻之后,可以切换该文本以显示本地化日期本身 (例如2016年11月5日15:30) 。
(默认值: 30 或一个月) 。 设置为0可始终显示日期,留空以始终显示相对时间。", "teaser": "预览帖子", "teaser.last-post": "最后– 显示最新的帖子,包括原帖,如果没有回复", @@ -35,7 +35,7 @@ "signature.no-images": "禁用签名中的图片", "signature.max-length": "签名最大长度", "composer": "编辑器设置", - "composer-help": "以下设置控制所示后期编辑器的功能和/或外观\n\\t\\t\\t\\t当用户创建新主题或回复现有主题时。", + "composer-help": "以下设置控制所示后期编辑器的功能和/或外观\n\t\t\t\t当用户创建新主题或回复现有主题时。", "composer.show-help": "显示“帮助”选项卡", "composer.enable-plugin-help": "允许插件将内容添加到帮助选项卡", "composer.custom-help": "自定义帮助文本", diff --git a/public/language/zh-CN/admin/settings/reputation.json b/public/language/zh-CN/admin/settings/reputation.json index e6f9640f81..ec3f657d1e 100644 --- a/public/language/zh-CN/admin/settings/reputation.json +++ b/public/language/zh-CN/admin/settings/reputation.json @@ -3,7 +3,7 @@ "disable": "禁用声望系统", "disable-down-voting": "禁用 踩", "votes-are-public": "所有投票是公开的", - "thresholds": "活动阈值", + "thresholds": "活动闸值", "min-rep-downvote": "踩帖子所需要声望的最小值", "min-rep-flag": "举报帖子需要的最小声望" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/uploads.json b/public/language/zh-CN/admin/settings/uploads.json index 6c52e63b11..2a4c42f933 100644 --- a/public/language/zh-CN/admin/settings/uploads.json +++ b/public/language/zh-CN/admin/settings/uploads.json @@ -3,7 +3,7 @@ "allow-files": "允许用户上传普通文件", "private": "使上传的文件私有化", "max-image-width": "缩小图片到指定宽度(单位像素)", - "max-image-width-help": "(像素单位,默认760像素,设置为0以禁用)", + "max-image-width-help": "(像素单位,默认 760 px,设置为0以禁用)", "max-file-size": "最大文件尺寸(单位 KiB)", "max-file-size-help": "(单位 KiB,默认2048KiB)", "allow-topic-thumbnails": "允许用户上传主题缩略图", @@ -16,11 +16,11 @@ "default-avatar": "访客默认头像", "upload": "上传", "profile-image-dimension": "个人资料相片尺寸", - "profile-image-dimension-help": "(使用像素作为单位,默认:128px)", + "profile-image-dimension-help": "(使用 px 作为单位,默认:128px)", "max-profile-image-size": "个人资料相片最大大小", - "max-profile-image-size-help": "(单位KiB,默认256KiB)", + "max-profile-image-size-help": "(单位 KiB ,默认256KiB)", "max-cover-image-size": "最大封面图片文件大小", - "max-cover-image-size-help": "(单位kb,默认:2048KiB)", + "max-cover-image-size-help": "(单位 KiB ,默认:2048KiB)", "keep-all-user-images": "在服务器上保留旧头像和旧的资料封面", "profile-covers": "资料封面", "default-covers": "默认封面图片", diff --git a/public/language/zh-CN/admin/settings/user.json b/public/language/zh-CN/admin/settings/user.json index 8d30a90366..7b13c89b95 100644 --- a/public/language/zh-CN/admin/settings/user.json +++ b/public/language/zh-CN/admin/settings/user.json @@ -33,11 +33,11 @@ "registration-type.help": "通常 - 用户可以通过/register页面注册
\n管理员批准 - 用户注册请求会被放入 请求队列 待管理员批准。
\n管理员批准 IP地址 - 新用户不受影响,已存在帐户的IP地址注册需要管理员批准。
\n邀请制 - 用户可以通过 用户 页面邀请其它用户。
\n管理员邀请制 - 只有管理员可以通过 用户admin/manage/users 页面邀请其它用户。
\n无注册 - 不开放用户注册。
", "registration.max-invites": "每个用户最大邀请数", "max-invites": "每个用户最大邀请数", - "max-invites-help": "无限制填0。管理员没有邀请限制
仅在邀请制时可用", + "max-invites-help": "无限制填 0 。管理员没有邀请限制
仅在邀请制时可用", "min-username-length": "最小用户名长度", "max-username-length": "最大用户名长度", "min-password-length": "最小密码长度", - "max-about-me-length": "最大自我介绍长度", + "max-about-me-length": "自我介绍的最大长度", "terms-of-use": "论坛使用条款 (留空即可禁用)", "user-search": "用户搜索", "user-search-results-per-page": "展示的结果数量", diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json index d7f706ff2c..a59beaf451 100644 --- a/public/language/zh-CN/error.json +++ b/public/language/zh-CN/error.json @@ -8,7 +8,7 @@ "invalid-pid": "无效帖子 ID", "invalid-uid": "无效用户 ID", "invalid-username": "无效用户名", - "invalid-email": "无效电子邮箱", + "invalid-email": "无效的电子邮箱", "invalid-title": "无效标题!", "invalid-user-data": "无效用户数据", "invalid-password": "无效密码", @@ -29,14 +29,14 @@ "username-too-long": "用户名太长", "password-too-long": "密码太长", "user-banned": "用户已禁止", - "user-banned-reason": "抱歉,此帐号已经被封号 (原因:%1)", + "user-banned-reason": "抱歉,此帐号已经被封禁 (原因:%1)", "user-too-new": "抱歉,您需要等待 %1 秒后,才可以发帖!", "blacklisted-ip": "对不起,您的 IP 地址已被社区禁用。如果您认为这是一个错误,请与管理员联系。", "ban-expiry-missing": "请提供此次禁言结束日期", "no-category": "版块不存在", "no-topic": "主题不存在", "no-post": "帖子不存在", - "no-group": "用户组不存在", + "no-group": "群组不存在", "no-user": "用户不存在", "no-teaser": "主题预览不存在", "no-privileges": "您没有权限执行此操作。", @@ -80,13 +80,13 @@ "invalid-image-type": "无效的图像类型。允许的类型有:%1", "invalid-image-extension": "无效的图像扩展", "invalid-file-type": "无效文件格式,允许的格式有:%1", - "group-name-too-short": "用户组名太短", - "group-name-too-long": "用户组名太长", - "group-already-exists": "用户组已存在", - "group-name-change-not-allowed": "不允许更改用户组名称", - "group-already-member": "已经是此用户组的成员", - "group-not-member": "不是此用户组的成员", - "group-needs-owner": "用户组需要指定至少一名组长", + "group-name-too-short": "群组名太短", + "group-name-too-long": "群组名太长", + "group-already-exists": "群组已存在", + "group-name-change-not-allowed": "不允许更改群组名称", + "group-already-member": "已经是此群组的成员", + "group-not-member": "不是此群组的成员", + "group-needs-owner": "群组需要指定至少一名群组所有者", "group-already-invited": "您已邀请该用户", "group-already-requested": "已提交您的请求", "post-already-deleted": "此帖已被删除", diff --git a/public/language/zh-CN/global.json b/public/language/zh-CN/global.json index f864947bf4..6555431231 100644 --- a/public/language/zh-CN/global.json +++ b/public/language/zh-CN/global.json @@ -31,7 +31,7 @@ "header.tags": "话题", "header.popular": "热门", "header.users": "会员", - "header.groups": "用户组", + "header.groups": "群组", "header.chats": "聊天", "header.notifications": "通知", "header.search": "搜索", @@ -45,7 +45,7 @@ "alert.success": "成功", "alert.error": "错误", "alert.banned": "封禁", - "alert.banned.message": "您刚刚被封禁,现在您将退出登录。", + "alert.banned.message": "您刚刚被封禁了,现在您将登出站点。", "alert.unfollow": "您已取消关注 %1!", "alert.follow": "您已关注 %1!", "online": "在线", diff --git a/public/language/zh-CN/groups.json b/public/language/zh-CN/groups.json index f99c7d23b1..3deaf8cb3a 100644 --- a/public/language/zh-CN/groups.json +++ b/public/language/zh-CN/groups.json @@ -1,35 +1,35 @@ { - "groups": "用户组", - "view_group": "查看用户组", - "owner": "组长", - "new_group": "创建用户组", - "no_groups_found": "尚无用户组信息", + "groups": "群组", + "view_group": "查看群组", + "owner": "群组所有者", + "new_group": "创建群组", + "no_groups_found": "尚无群组信息", "pending.accept": "接受", "pending.reject": "拒绝", - "pending.accept_all": "接受全部", - "pending.reject_all": "拒绝全部", + "pending.accept_all": "全部同意", + "pending.reject_all": "全部拒绝", "pending.none": "暂时没有待加入的成员", "invited.none": "暂时没有接受邀请的成员", "invited.uninvite": "取消邀请", - "invited.search": "选择用户加入用户组", + "invited.search": "选择用户加入群组", "invited.notification_title": "您已被邀请加入 %1", - "request.notification_title": "来自 %1 的用户组成员请求", + "request.notification_title": "来自 %1 的群组成员请求", "request.notification_text": "%1 已被邀请加入 %2", "cover-save": "保存", "cover-saving": "正在保存", - "details.title": "用户组信息", + "details.title": "群组信息", "details.members": "成员列表", "details.pending": "待加入成员", "details.invited": "已邀请成员", - "details.has_no_posts": "此用户组的会员尚未发表任何帖子。", + "details.has_no_posts": "此群组的会员尚未发表任何帖子。", "details.latest_posts": "最新帖子", "details.private": "私有", "details.disableJoinRequests": "禁止申请加入用户组", "details.grant": "授予/取消管理权", "details.kick": "踢出用户组", - "details.kick_confirm": "您确定要将此成员从用户组中移除吗?", - "details.owner_options": "用户组管理", - "details.group_name": "用户组名", + "details.kick_confirm": "您确定要将此成员从群组中移除吗?", + "details.owner_options": "群组管理", + "details.group_name": "群组名", "details.member_count": "用户组成员数", "details.creation_date": "创建时间", "details.description": "描述", @@ -38,21 +38,21 @@ "details.change_colour": "更改颜色", "details.badge_text": "徽章文本", "details.userTitleEnabled": "显示组内称号", - "details.private_help": "启用此选项后,加入用户组需要组长审批。", + "details.private_help": "启用此选项后,加入群组需要组长审批。", "details.hidden": "隐藏", - "details.hidden_help": "启用此选项后,用户组将不在用户组列表中展现,成员只能通过邀请加入。", - "details.delete_group": "删除用户组", - "details.private_system_help": "系统禁用了私有用户组,这个选项不起任何作用", - "event.updated": "用户组信息已更新", - "event.deleted": "用户组 \"%1\" 已被删除", + "details.hidden_help": "启用此选项后,群组将不在群组列表中展现,成员只能通过邀请加入。", + "details.delete_group": "删除群组", + "details.private_system_help": "系统禁用了私有群组,这个选项不起任何作用", + "event.updated": "群组信息已更新", + "event.deleted": "群组 \"%1\" 已被删除", "membership.accept-invitation": "接受邀请", "membership.invitation-pending": "邀请中", - "membership.join-group": "加入用户组", - "membership.leave-group": "退出用户组", + "membership.join-group": "加入群组", + "membership.leave-group": "退出群组", "membership.reject": "拒绝", - "new-group.group_name": "组名: ", - "upload-group-cover": "上传组封面", - "bulk-invite-instructions": "输入您要邀请加入此用户组的用户名,多个用户以逗号分隔", + "new-group.group_name": "群组名: ", + "upload-group-cover": "上传群组封面", + "bulk-invite-instructions": "输入您要邀请加入此群组的用户名,多个用户以逗号分隔", "bulk-invite": "批量邀请", "remove_group_cover_confirm": "确定要移除封面图片吗?" } \ No newline at end of file diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index 94874032f2..235ec2c80b 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/modules.json @@ -20,7 +20,7 @@ "chat.three_months": "3个月", "chat.delete_message_confirm": "确认删除此消息吗?", "chat.add-users-to-room": "向此聊天室中添加成员", - "composer.compose": "编写", + "composer.compose": "编写帮助", "composer.show_preview": "显示预览", "composer.hide_preview": "隐藏预览", "composer.user_said_in": "%1 在 %2 中说:", @@ -38,7 +38,7 @@ "composer.upload-picture": "上传图片", "composer.upload-file": "上传文件", "composer.zen_mode": "无干扰模式", - "composer.select_category": "选择一个板块", + "composer.select_category": "选择一个版块", "bootbox.ok": "确认", "bootbox.cancel": "取消", "bootbox.confirm": "确认", diff --git a/public/language/zh-CN/notifications.json b/public/language/zh-CN/notifications.json index ae58756a48..850c504005 100644 --- a/public/language/zh-CN/notifications.json +++ b/public/language/zh-CN/notifications.json @@ -24,7 +24,7 @@ "upvoted_your_post_in_dual": "%1%2%3 赞了您的帖子。", "upvoted_your_post_in_multiple": "%1 和 %2 个其他人在 %3 赞了您的帖子。", "moved_your_post": "您的帖子已被 %1 移动到了 %2", - "moved_your_topic": "%1 移动到了 %2", + "moved_your_topic": "%1 移动了 %2", "user_flagged_post_in": "%1%2 标记了一个帖子", "user_flagged_post_in_dual": "%1%2%3 举报了一个帖子", "user_flagged_post_in_multiple": "%1 和 %2 个其他人在 %3 举报了一个帖子", @@ -42,7 +42,7 @@ "new_register_multiple": "有 %1 条注册申请等待批准。", "flag_assigned_to_you": "举报 %1 已经被指派给你", "email-confirmed": "电子邮箱已确认", - "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已全面激活。", - "email-confirm-error-message": "验证您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。", + "email-confirmed-message": "感谢您验证您的电子邮箱。您的帐户现已完全激活。", + "email-confirm-error-message": "验证的您电子邮箱地址时出现了问题。可能是因为验证码无效或已过期。", "email-confirm-sent": "确认邮件已发送。" } \ No newline at end of file diff --git a/public/language/zh-CN/pages.json b/public/language/zh-CN/pages.json index 533df5539c..c76c550395 100644 --- a/public/language/zh-CN/pages.json +++ b/public/language/zh-CN/pages.json @@ -22,9 +22,9 @@ "registration-complete": "注册完成", "login": "登录帐号", "reset": "重置帐户密码", - "categories": "板块", - "groups": "用户组", - "group": "%1 的用户组", + "categories": "版块", + "groups": "群组", + "group": "%1 的群组", "chats": "聊天", "chat": "与 %1 聊天", "flags": "举报", @@ -38,7 +38,7 @@ "account/followers": "关注 %1 的人", "account/posts": "%1 发布的帖子", "account/topics": "%1 创建的主题", - "account/groups": "%1 的用户组", + "account/groups": "%1 的群组", "account/bookmarks": "%1 收藏的帖子", "account/settings": "用户设置", "account/watched": "主题已被 %1 关注", diff --git a/public/language/zh-CN/register.json b/public/language/zh-CN/register.json index 11a0706cc9..af02bf10dd 100644 --- a/public/language/zh-CN/register.json +++ b/public/language/zh-CN/register.json @@ -12,7 +12,7 @@ "password_placeholder": "输入密码", "confirm_password": "确认密码", "confirm_password_placeholder": "再次输入密码", - "register_now_button": "马上注册", + "register_now_button": "立即注册", "alternative_registration": "其他方式注册", "terms_of_use": "使用条款", "agree_to_terms_of_use": "我同意使用条款", diff --git a/public/language/zh-CN/search.json b/public/language/zh-CN/search.json index 4633e600ed..2d6b500297 100644 --- a/public/language/zh-CN/search.json +++ b/public/language/zh-CN/search.json @@ -6,8 +6,8 @@ "titles": "标题", "titles-posts": "标题和回帖", "posted-by": "发表", - "in-categories": "在版面", - "search-child-categories": "搜索子版面", + "in-categories": "在版块", + "search-child-categories": "搜索子版块", "has-tags": "有标签", "reply-count": "回复数", "at-least": "至少", @@ -31,7 +31,7 @@ "number-of-views": "查看数", "topic-start-date": "主题开始日期", "username": "用户名", - "category": "板块", + "category": "版块", "descending": "倒序", "ascending": "顺序", "save-preferences": "保存设置", diff --git a/public/language/zh-CN/topic.json b/public/language/zh-CN/topic.json index 5491cffe0d..cbd70048ef 100644 --- a/public/language/zh-CN/topic.json +++ b/public/language/zh-CN/topic.json @@ -38,7 +38,7 @@ "login_to_subscribe": "请注册或登录后,再订阅此主题。", "markAsUnreadForAll.success": "将全部主题标为未读。", "mark_unread": "标记为未读", - "mark_unread.success": "未读话题", + "mark_unread.success": "主题已被标记为未读。", "watch": "关注", "unwatch": "取消关注", "watch.title": "当此主题有新回复时,通知我", @@ -48,7 +48,7 @@ "not-watching": "未关注", "ignoring": "忽略中", "watching.description": "有新回复时通知我。
在未读主题中显示。", - "not-watching.description": "不要在有新回复时通知我。
如果这个分类未被忽略则在未读主题中显示。", + "not-watching.description": "不要在有新回复时通知我。
如果这个版块未被忽略则在未读主题中显示。", "ignoring.description": "不要在有新回复时通知我。
不要在未读主题中显示该主题。", "thread_tools.title": "主题工具", "thread_tools.markAsUnreadForAll": "标记全部未读", @@ -70,8 +70,8 @@ "post_delete_confirm": "确定删除此帖吗?", "post_restore_confirm": "确定恢复此帖吗?", "post_purge_confirm": "确认清除此回帖吗?", - "load_categories": "正在载入板块", - "disabled_categories_note": "停用的板块为灰色", + "load_categories": "正在载入版块", + "disabled_categories_note": "停用的版块为灰色", "confirm_move": "移动", "confirm_fork": "分割", "bookmark": "书签", diff --git a/public/language/zh-CN/user.json b/public/language/zh-CN/user.json index 1b0307e789..1f7842a16c 100644 --- a/public/language/zh-CN/user.json +++ b/public/language/zh-CN/user.json @@ -1,5 +1,5 @@ { - "banned": "封禁", + "banned": "已封禁", "offline": "离线", "username": "用户名", "joindate": "注册日期", @@ -8,7 +8,7 @@ "confirm_email": "确认电子邮箱", "account_info": "账户信息", "ban_account": "封禁账户", - "ban_account_confirm": "您确定封禁这位用户吗?", + "ban_account_confirm": "您确定要封禁这位用户吗?", "unban_account": "解禁账户", "delete_account": "删除帐号", "delete_account_confirm": "确认要删除您的帐户吗?
此操作是不可逆转的,您将无法恢复您的任何数据

请输入您的用户名,确认您想要删除此帐户。", @@ -108,8 +108,8 @@ "scroll_to_my_post": "在提交回复之后显示新回复", "follow_topics_you_reply_to": "关注你回复过的主题", "follow_topics_you_create": "关注你创建的主题", - "grouptitle": "用户组标题", - "no-group-title": "不展示用户组称号", + "grouptitle": "群组标题", + "no-group-title": "不展示群组称号", "select-skin": "选择皮肤", "select-homepage": "选择首页", "homepage": "首页", @@ -117,8 +117,8 @@ "custom_route": "自定义首页路由", "custom_route_help": "输入路由名称,前面不需要斜杠 ( 例如, \"recent\" 或 \"popular\" )", "sso.title": "单点登录服务", - "sso.associated": "关联到", - "sso.not-associated": "点击这里关联", + "sso.associated": "已关联到", + "sso.not-associated": "点击这里来关联", "info.latest-flags": "最新举报", "info.no-flags": "没有找到被举报的帖子", "info.ban-history": "最近封禁历史", @@ -129,7 +129,7 @@ "info.banned-no-reason": "没有原因", "info.username-history": "历史用户名", "info.email-history": "历史邮箱", - "info.moderation-note": "版主留言", - "info.moderation-note.success": "修改未保存", - "info.moderation-note.add": "添加注解" + "info.moderation-note": "版主备注", + "info.moderation-note.success": "版主备注已保存", + "info.moderation-note.add": "添加备注" } \ No newline at end of file diff --git a/public/language/zh-CN/users.json b/public/language/zh-CN/users.json index a6a2267467..bdc9eda8de 100644 --- a/public/language/zh-CN/users.json +++ b/public/language/zh-CN/users.json @@ -15,7 +15,7 @@ "recent_topics": "最新主题", "popular_topics": "热门主题", "unread_topics": "未读主题", - "categories": "版面", + "categories": "版块", "tags": "话题", "no-users-found": "未找到匹配的用户!" } \ No newline at end of file diff --git a/public/src/admin/modules/search.js b/public/src/admin/modules/search.js index c52008fdab..7694b21b3a 100644 --- a/public/src/admin/modules/search.js +++ b/public/src/admin/modules/search.js @@ -1,6 +1,5 @@ 'use strict'; - define('admin/modules/search', ['mousetrap'], function (mousetrap) { var search = {}; @@ -11,16 +10,17 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) { var namespace = params.namespace; var translations = params.translations; var title = params.title; + var escaped = utils.escapeRegexChars(term); var results = translations // remove all lines without a match - .replace(new RegExp('^(?:(?!' + term + ').)*$', 'gmi'), '') + .replace(new RegExp('^(?:(?!' + escaped + ').)*$', 'gmi'), '') // remove lines that only match the title .replace(new RegExp('(^|\\n).*?' + title + '.*?(\\n|$)', 'g'), '') // get up to 25 characters of context on both sides of the match // and wrap the match in a `.search-match` element .replace( - new RegExp('^[\\s\\S]*?(.{0,25})(' + term + ')(.{0,25})[\\s\\S]*?$', 'gmi'), + new RegExp('^[\\s\\S]*?(.{0,25})(' + escaped + ')(.{0,25})[\\s\\S]*?$', 'gmi'), '...$1$2$3...
' ) // collapse whitespace @@ -28,7 +28,7 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) { .trim(); title = title.replace( - new RegExp('(^.*?)(' + term + ')(.*?$)', 'gi'), + new RegExp('(^.*?)(' + escaped + ')(.*?$)', 'gi'), '$1$2$3' ); @@ -123,7 +123,7 @@ define('admin/modules/search', ['mousetrap'], function (mousetrap) { menu.children('.result').remove(); - var len = value.length; + var len = /\W/.test(value) ? 3 : value.length; var results; menu.toggleClass('state-start-typing', len === 0); diff --git a/public/src/client/account/edit/password.js b/public/src/client/account/edit/password.js index d2239b36a0..44723014ce 100644 --- a/public/src/client/account/edit/password.js +++ b/public/src/client/account/edit/password.js @@ -1,7 +1,7 @@ 'use strict'; -define('forum/account/edit/password', ['forum/account/header', 'translator'], function (header, translator) { +define('forum/account/edit/password', ['forum/account/header', 'translator', 'zxcvbn'], function (header, translator, zxcvbn) { var AccountEditPassword = {}; AccountEditPassword.init = function () { @@ -20,6 +20,7 @@ define('forum/account/edit/password', ['forum/account/header', 'translator'], fu var passwordsmatch = false; function onPasswordChanged() { + var passwordStrength = zxcvbn(password.val()); passwordvalid = false; if (password.val().length < ajaxify.data.minimumPasswordLength) { showError(password_notify, '[[user:change_password_error_length]]'); @@ -29,6 +30,8 @@ define('forum/account/edit/password', ['forum/account/header', 'translator'], fu showError(password_notify, '[[user:password_same_as_username]]'); } else if (password.val() === ajaxify.data.email) { showError(password_notify, '[[user:password_same_as_email]]'); + } else if (passwordStrength.score < ajaxify.data.minimumPasswordStrength) { + showError(password_notify, '[[user:weak_password]]'); } else { showSuccess(password_notify); passwordvalid = true; diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 582c578f65..3dca09bc38 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -98,7 +98,11 @@ define('forum/chats', [ } loading = true; var start = parseInt($('.chat-content').children('[data-index]').first().attr('data-index'), 10) + 1; - socket.emit('modules.chats.getMessages', { roomId: roomId, uid: uid, start: start }, function (err, data) { + socket.emit('modules.chats.getMessages', { + roomId: roomId, + uid: uid, + start: start, + }, function (err, data) { if (err) { return app.alertError(err.message); } @@ -122,7 +126,7 @@ define('forum/chats', [ Chats.addEditDeleteHandler = function (element, roomId) { element.on('click', '[data-action="edit"]', function () { var messageId = $(this).parents('[data-mid]').attr('data-mid'); - var inputEl = components.get('chat/input'); + var inputEl = $('[data-roomid="' + roomId + '"] [component="chat/input"]'); messages.prepEdit(inputEl, messageId, roomId); }).on('click', '[data-action="delete"]', function () { var messageId = $(this).parents('[data-mid]').attr('data-mid'); @@ -170,7 +174,10 @@ define('forum/chats', [ if (oldName === newName) { return; } - socket.emit('modules.chats.renameRoom', { roomId: roomId, newName: newName }, function (err) { + socket.emit('modules.chats.renameRoom', { + roomId: roomId, + newName: newName, + }, function (err) { if (err) { return app.alertError(err.message); } @@ -235,10 +242,15 @@ define('forum/chats', [ if (event.item === app.user.username) { return; } - socket.emit('modules.chats.addUserToRoom', { roomId: data.roomId, username: event.item }, function (err) { + socket.emit('modules.chats.addUserToRoom', { + roomId: data.roomId, + username: event.item, + }, function (err) { if (err) { app.alertError(err.message); - tagEl.tagsinput('remove', event.item, { nouser: true }); + tagEl.tagsinput('remove', event.item, { + nouser: true, + }); } }); }); @@ -262,7 +274,10 @@ define('forum/chats', [ if (event.options && event.options.nouser) { return; } - socket.emit('modules.chats.removeUserFromRoom', { roomId: data.roomId, username: event.item }, function (err) { + socket.emit('modules.chats.removeUserFromRoom', { + roomId: data.roomId, + username: event.item, + }, function (err) { if (err) { return app.alertError(err.message); } @@ -325,7 +340,12 @@ define('forum/chats', [ } else { var recentEl = components.get('chat/recent'); templates.parse('partials/chats/recent_room', { - rooms: { roomId: data.roomId, lastUser: data.message.fromUser, usernames: data.message.fromUser.username, unread: true }, + rooms: { + roomId: data.roomId, + lastUser: data.message.fromUser, + usernames: data.message.fromUser.username, + unread: true, + }, }, function (html) { translator.translate(html, function (translated) { recentEl.prepend(translated); @@ -347,7 +367,7 @@ define('forum/chats', [ }; Chats.resizeMainWindow = function () { - var messagesList = $('.expanded-chat .chat-content'); + var messagesList = $('.expanded-chat .chat-content'); if (messagesList.length) { var margin = $('.expanded-chat ul').outerHeight(true) - $('.expanded-chat ul').height(); diff --git a/public/src/client/register.js b/public/src/client/register.js index 329762271e..8070263906 100644 --- a/public/src/client/register.js +++ b/public/src/client/register.js @@ -1,7 +1,7 @@ 'use strict'; -define('forum/register', ['translator'], function (translator) { +define('forum/register', ['translator', 'zxcvbn'], function (translator, zxcvbn) { var Register = {}; var validationError = false; var successIcon = ''; @@ -170,6 +170,7 @@ define('forum/register', ['translator'], function (translator) { function validatePassword(password, password_confirm) { var password_notify = $('#password-notify'); var password_confirm_notify = $('#password-confirm-notify'); + var passwordStrength = zxcvbn(password); if (password.length < ajaxify.data.minimumPasswordLength) { showError(password_notify, '[[user:change_password_error_length]]'); @@ -181,6 +182,8 @@ define('forum/register', ['translator'], function (translator) { showError(password_notify, '[[user:password_same_as_username]]'); } else if (password === $('#email').val()) { showError(password_notify, '[[user:password_same_as_email]]'); + } else if (passwordStrength.score < ajaxify.data.minimumPasswordStrength) { + showError(password_notify, '[[user:weak_password]]'); } else { showSuccess(password_notify, successIcon); } diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 1d4150b179..d05829c79d 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -75,7 +75,9 @@ define('chat', [ }); } } else { - socket.emit('modules.chats.loadRoom', { roomId: data.roomId }, function (err, roomData) { + socket.emit('modules.chats.loadRoom', { + roomId: data.roomId, + }, function (err, roomData) { if (err) { return app.alertError(err.message); } @@ -111,7 +113,10 @@ define('chat', [ }; module.loadChatsDropdown = function (chatsListEl) { - socket.emit('modules.chats.getRecentChats', { uid: app.user.uid, after: 0 }, function (err, data) { + socket.emit('modules.chats.getRecentChats', { + uid: app.user.uid, + after: 0, + }, function (err, data) { if (err) { return app.alertError(err.message); } @@ -163,7 +168,7 @@ define('chat', [ var dragged = false; chatModal.attr('id', 'chat-modal-' + data.roomId); - chatModal.attr('roomId', data.roomId); + chatModal.attr('data-roomid', data.roomId); chatModal.attr('intervalId', 0); chatModal.attr('UUID', uuid); chatModal.css('position', 'fixed'); @@ -211,7 +216,7 @@ define('chat', [ components.get('chat/input').val(text); }); - ajaxify.go('user/' + app.user.userslug + '/chats/' + chatModal.attr('roomId')); + ajaxify.go('user/' + app.user.userslug + '/chats/' + chatModal.attr('data-roomid')); module.close(chatModal); } @@ -252,14 +257,14 @@ define('chat', [ messagesEl.css('height', module.calculateChatListHeight(chatModal)); }); - Chats.addRenameHandler(chatModal.attr('roomId'), chatModal.find('[component="chat/room/name"]')); + Chats.addRenameHandler(chatModal.attr('data-roomid'), chatModal.find('[component="chat/room/name"]')); - Chats.addSendHandlers(chatModal.attr('roomId'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn')); + Chats.addSendHandlers(chatModal.attr('data-roomid'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn')); Chats.createTagsInput(chatModal.find('.users-tag-input'), data); Chats.createAutoComplete(chatModal.find('[component="chat/input"]')); - Chats.addScrollHandler(chatModal.attr('roomId'), data.uid, chatModal.find('.chat-content')); + Chats.addScrollHandler(chatModal.attr('data-roomid'), data.uid, chatModal.find('.chat-content')); taskbar.push('chat', chatModal.attr('UUID'), { title: data.roomName || (data.users.length ? data.users[0].username : ''), @@ -314,7 +319,7 @@ define('chat', [ ChatsMessages.scrollToBottom(chatModal.find('.chat-content')); module.bringModalToTop(chatModal); module.focusInput(chatModal); - socket.emit('modules.chats.markRead', chatModal.attr('roomId')); + socket.emit('modules.chats.markRead', chatModal.attr('data-roomid')); var env = utils.findBootstrapEnvironment(); if (env === 'xs' || env === 'sm') { diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index e4defc99d5..7afb07b24f 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -97,6 +97,9 @@ // and the strings of untranslated text in between var toTranslate = []; + // to store the state of if we're currently in a top-level token for later + var inToken = false; + // split a translator string into an array of tokens // but don't split by commas inside other translator strings function split(text) { @@ -141,6 +144,8 @@ // set the last break to our current // spot since we just broke the string lastBreak = cursor; + // we're in a token now + inToken = true; // the current level of nesting of the translation strings var level = 0; @@ -176,6 +181,8 @@ invalidTextRegex.test(sliced[0])) { cursor += 1; lastBreak -= 2; + // no longer in a token + inToken = false; if (level > 0) { level -= 1; } else { @@ -191,18 +198,26 @@ // if we're at the base level, then this is the end if (level === 0) { // so grab the name and args - var result = split(str.slice(lastBreak, cursor)); + var currentSlice = str.slice(lastBreak, cursor); + var result = split(currentSlice); var name = result[0]; var args = result.slice(1); + // make a backup based on the raw string of the token + // if there are arguments to the token + var backup = ''; + if (args && args.length) { + backup = this.translate('[[' + currentSlice + '[['); + } // add the translation promise to the array - toTranslate.push(this.translateKey(name, args)); + toTranslate.push(this.translateKey(name, args, backup)); // skip past the ending brackets cursor += 2; // set this as our last break lastBreak = cursor; // and we're no longer in a translation string, // so continue with the main loop + inToken = false; break; } // otherwise we lower the level @@ -219,8 +234,16 @@ cursor += 1; } + // ending string of source + var last = str.slice(lastBreak); + + // if we were mid-token, treat it as invalid + if (inToken) { + last = this.translate('[[' + last); + } + // add the remaining text after the last translation string - toTranslate.push(str.slice(lastBreak, cursor + 2)); + toTranslate.push(last); // and return a promise for the concatenated translated string return Promise.all(toTranslate).then(function (translated) { @@ -232,9 +255,10 @@ * Translates a specific key and array of arguments * @param {string} name - Translation key (ex. 'global:home') * @param {string[]} args - Arguments for `%1`, `%2`, etc + * @param {string|Promise} backup - Text to use in case the key can't be found * @returns {Promise} */ - Translator.prototype.translateKey = function translateKey(name, args) { + Translator.prototype.translateKey = function translateKey(name, args, backup) { var self = this; var result = name.split(':', 2); @@ -251,29 +275,27 @@ } var translation = this.getTranslation(namespace, key); - var argsToTranslate = args.map(function (arg) { - return string(arg).collapseWhitespace().decodeHTMLEntities().escapeHTML().s; - }).map(function (arg) { - return self.translate(arg); - }); - - // so we can await all promises at once - argsToTranslate.unshift(translation); - - return Promise.all(argsToTranslate).then(function (result) { - var translated = result[0]; - var translatedArgs = result.slice(1); - + return translation.then(function (translated) { + // check if the translation is missing first if (!translated) { warn('Missing translation "' + name + '"'); - return key; + return backup || key; } - var out = translated; - translatedArgs.forEach(function (arg, i) { - var escaped = arg.replace(/%(?=\d)/g, '%').replace(/\\,/g, ','); - out = out.replace(new RegExp('%' + (i + 1), 'g'), escaped); + + var argsToTranslate = args.map(function (arg) { + return string(arg).collapseWhitespace().decodeHTMLEntities().escapeHTML().s; + }).map(function (arg) { + return self.translate(arg); + }); + + return Promise.all(argsToTranslate).then(function (translatedArgs) { + var out = translated; + translatedArgs.forEach(function (arg, i) { + var escaped = arg.replace(/%(?=\d)/g, '%').replace(/\\,/g, ','); + out = out.replace(new RegExp('%' + (i + 1), 'g'), escaped); + }); + return out; }); - return out; }); }; @@ -281,7 +303,7 @@ * Load translation file (or use a cached version), and optionally return the translation of a certain key * @param {string} namespace - The file name of the translation namespace * @param {string} [key] - The key of the specific translation to getJSON - * @returns {Promise} + * @returns {Promise|Promise} */ Translator.prototype.getTranslation = function getTranslation(namespace, key) { var translation; diff --git a/bcrypt.js b/src/bcrypt.js similarity index 100% rename from bcrypt.js rename to src/bcrypt.js diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index c38fec527e..564d05d673 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -86,7 +86,7 @@ module.exports = function (Categories) { privileges.topics.filterTids('read', tids, uid, next); }, function (tids, next) { - getTopics(tids, next); + getTopics(tids, uid, next); }, function (topics, next) { assignTopicsToCategories(categoryData, topics); @@ -98,7 +98,7 @@ module.exports = function (Categories) { ], callback); }; - function getTopics(tids, callback) { + function getTopics(tids, uid, callback) { var topicData; async.waterfall([ function (next) { @@ -119,7 +119,7 @@ module.exports = function (Categories) { async.parallel({ categoryData: async.apply(Categories.getCategoriesFields, cids, ['cid', 'parentCid']), - teasers: async.apply(topics.getTeasers, _topicData), + teasers: async.apply(topics.getTeasers, _topicData, uid), }, next); }, function (results, next) { diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js index 3c2e57bec4..d659b0153f 100644 --- a/src/controllers/accounts/edit.js +++ b/src/controllers/accounts/edit.js @@ -80,6 +80,7 @@ function renderRoute(name, req, res, next) { if (name === 'password') { userData.minimumPasswordLength = parseInt(meta.config.minimumPasswordLength, 10); + userData.minimumPasswordStrength = parseInt(meta.config.minimumPasswordStrength || 0, 10); } userData.title = '[[pages:account/edit/' + name + ', ' + userData.username + ']]'; diff --git a/src/controllers/errors.js b/src/controllers/errors.js index 6ab0dc6471..0e61b557cc 100644 --- a/src/controllers/errors.js +++ b/src/controllers/errors.js @@ -43,13 +43,14 @@ exports.handleErrors = function (err, req, res, next) { // eslint-disable-line n return res.status(403).type('text/plain').send(err.message); } - if (parseInt(err.status, 10) === 302 && err.path) { - return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path); + var status = parseInt(err.status, 10); + if ((status === 302 || status === 308) && err.path) { + return res.locals.isAPI ? res.status(status).json(err.path) : res.redirect(err.path); } winston.error(req.path + '\n', err.stack); - res.status(err.status || 500); + res.status(status || 500); var path = String(req.path || ''); if (res.locals.isAPI) { diff --git a/src/controllers/index.js b/src/controllers/index.js index 93b8e9a383..6cebf29d8f 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -48,7 +48,11 @@ Controllers.home = function (req, res, next) { var hook = 'action:homepage.get:' + route; if (plugins.hasListeners(hook)) { - return plugins.fireHook(hook, { req: req, res: res, next: next }); + return plugins.fireHook(hook, { + req: req, + res: res, + next: next, + }); } if (route === 'categories' || route === '/') { @@ -85,7 +89,15 @@ Controllers.reset = function (req, res, next) { displayExpiryNotice: req.session.passwordExpired, code: req.params.code, minimumPasswordLength: parseInt(meta.config.minimumPasswordLength, 10), - breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[reset_password:reset_password]]', url: '/reset' }, { text: '[[reset_password:update_password]]' }]), + breadcrumbs: helpers.buildBreadcrumbs([ + { + text: '[[reset_password:reset_password]]', + url: '/reset', + }, + { + text: '[[reset_password:update_password]]', + }, + ]), title: '[[pages:reset]]', }); @@ -94,7 +106,9 @@ Controllers.reset = function (req, res, next) { } else { res.render('reset', { code: null, - breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[reset_password:reset_password]]' }]), + breadcrumbs: helpers.buildBreadcrumbs([{ + text: '[[reset_password:reset_password]]', + }]), title: '[[pages:reset]]', }); } @@ -124,7 +138,9 @@ Controllers.login = function (req, res, next) { data.allowLocalLogin = parseInt(meta.config.allowLocalLogin, 10) === 1 || parseInt(req.query.local, 10) === 1; data.allowRegistration = registrationType === 'normal' || registrationType === 'admin-approval' || registrationType === 'admin-approval-ip'; data.allowLoginWith = '[[login:' + allowLoginWith + ']]'; - data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[global:login]]' }]); + data.breadcrumbs = helpers.buildBreadcrumbs([{ + text: '[[global:login]]', + }]); data.error = req.flash('error')[0] || errorText; data.title = '[[pages:login]]'; @@ -171,7 +187,11 @@ Controllers.register = function (req, res, next) { } }, function (next) { - plugins.fireHook('filter:parse.post', { postData: { content: meta.config.termsOfUse || '' } }, next); + plugins.fireHook('filter:parse.post', { + postData: { + content: meta.config.termsOfUse || '', + }, + }, next); }, ], function (err, termsOfUse) { if (err) { @@ -188,8 +208,11 @@ Controllers.register = function (req, res, next) { data.minimumUsernameLength = parseInt(meta.config.minimumUsernameLength, 10); data.maximumUsernameLength = parseInt(meta.config.maximumUsernameLength, 10); data.minimumPasswordLength = parseInt(meta.config.minimumPasswordLength, 10); + data.minimumPasswordStrength = parseInt(meta.config.minimumPasswordStrength || 0, 10); data.termsOfUse = termsOfUse.postData.content; - data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[register:register]]' }]); + data.breadcrumbs = helpers.buildBreadcrumbs([{ + text: '[[register:register]]', + }]); data.regFormEntry = []; data.error = req.flash('error')[0] || errorText; data.title = '[[pages:register]]'; @@ -333,7 +356,9 @@ Controllers.outgoing = function (req, res, next) { res.render('outgoing', { outgoing: validator.escape(String(url)), title: meta.config.title, - breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[notifications:outgoing_link]]' }]), + breadcrumbs: helpers.buildBreadcrumbs([{ + text: '[[notifications:outgoing_link]]', + }]), }); }; @@ -341,7 +366,9 @@ Controllers.termsOfUse = function (req, res, next) { if (!meta.config.termsOfUse) { return next(); } - res.render('tos', { termsOfUse: meta.config.termsOfUse }); + res.render('tos', { + termsOfUse: meta.config.termsOfUse, + }); }; Controllers.ping = function (req, res) { diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 1d813868ae..adfad461e2 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -67,6 +67,7 @@ topicsController.get = function (req, res, callback) { settings = results.settings; var postCount = parseInt(results.topic.postcount, 10); pageCount = Math.max(1, Math.ceil(postCount / settings.postsPerPage)); + results.topic.postcount = postCount; if (utils.isNumber(req.params.post_index) && (req.params.post_index < 1 || req.params.post_index > postCount)) { return helpers.redirect(res, '/topic/' + req.params.topic_id + '/' + req.params.slug + (req.params.post_index > postCount ? '/' + postCount : '')); diff --git a/src/database/mongo.js b/src/database/mongo.js index c62a22d352..91b41306f1 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -166,7 +166,7 @@ }; module.checkCompatibility = function (callback) { - var mongoPkg = require.main.require('./node_modules/mongodb/package.json'); + 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.')); diff --git a/src/meta/configs.js b/src/meta/configs.js index 82d8640e4c..daaa807d19 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -101,16 +101,21 @@ module.exports = function (Meta) { } function updateConfig(config) { + updateLocalConfig(config); pubsub.publish('config:update', config); } + function updateLocalConfig(config) { + for (var field in config) { + if (config.hasOwnProperty(field)) { + Meta.config[field] = config[field]; + } + } + } + pubsub.on('config:update', function onConfigReceived(config) { if (typeof config === 'object' && Meta.config) { - for (var field in config) { - if (config.hasOwnProperty(field)) { - Meta.config[field] = config[field]; - } - } + updateLocalConfig(config); } }); diff --git a/src/meta/js.js b/src/meta/js.js index 03156b4cdd..71f74084b1 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -20,8 +20,8 @@ module.exports = function (Meta) { target: {}, scripts: { base: [ - './node_modules/jquery/dist/jquery.js', - './node_modules/socket.io-client/dist/socket.io.js', + 'node_modules/jquery/dist/jquery.js', + 'node_modules/socket.io-client/dist/socket.io.js', 'public/vendor/jquery/timeago/jquery.timeago.js', 'public/vendor/jquery/js/jquery.form.min.js', 'public/vendor/visibility/visibility.min.js', @@ -35,14 +35,14 @@ module.exports = function (Meta) { 'public/vendor/tinycon/tinycon.js', 'public/vendor/xregexp/xregexp.js', 'public/vendor/xregexp/unicode/unicode-base.js', - './node_modules/templates.js/lib/templates.js', + 'node_modules/templates.js/lib/templates.js', 'public/src/utils.js', 'public/src/sockets.js', 'public/src/app.js', 'public/src/ajaxify.js', 'public/src/overrides.js', 'public/src/widgets.js', - './node_modules/promise-polyfill/promise.js', + 'node_modules/promise-polyfill/promise.js', ], // files listed below are only available client-side, or are bundled in to reduce # of network requests on cold load @@ -84,11 +84,12 @@ module.exports = function (Meta) { // modules listed below are built (/src/modules) so they can be defined anonymously modules: { - 'Chart.js': './node_modules/chart.js/dist/Chart.min.js', - 'mousetrap.js': './node_modules/mousetrap/mousetrap.min.js', + 'Chart.js': 'node_modules/chart.js/dist/Chart.min.js', + 'mousetrap.js': 'node_modules/mousetrap/mousetrap.min.js', 'jqueryui.js': 'public/vendor/jquery/js/jquery-ui.js', 'buzz.js': 'public/vendor/buzz/buzz.js', - 'cropper.js': './node_modules/cropperjs/dist/cropper.min.js', + 'cropper.js': 'node_modules/cropperjs/dist/cropper.min.js', + 'zxcvbn.js': 'node_modules/zxcvbn/dist/zxcvbn.js', }, }, }; @@ -110,7 +111,9 @@ module.exports = function (Meta) { } if (filePath.endsWith('.min.js')) { - minified = { code: buffer.toString() }; + minified = { + code: buffer.toString(), + }; return cb(); } @@ -193,10 +196,10 @@ module.exports = function (Meta) { function clearModules(callback) { var builtPaths = moduleDirs.map(function (p) { - return '../../build/public/src/' + p; + return path.join(__dirname, '../../build/public/src', p); }); async.each(builtPaths, function (builtPath, next) { - rimraf(path.join(__dirname, builtPath), next); + rimraf(builtPath, next); }, function (err) { callback(err); }); @@ -311,7 +314,7 @@ module.exports = function (Meta) { } Meta.js.target[target].scripts = Meta.js.target[target].scripts.map(function (script) { - return path.relative(basePath, script).replace(/\\/g, '/'); + return path.resolve(basePath, script).replace(/\\/g, '/'); }); callback(); @@ -325,7 +328,7 @@ module.exports = function (Meta) { }; Meta.js.commitToFile = function (target, callback) { - fs.writeFile(path.join(__dirname, '../../build/public/' + target), Meta.js.target[target].cache, function (err) { + fs.writeFile(path.join(__dirname, '../../build/public', target), Meta.js.target[target].cache, function (err) { callback(err); }); }; @@ -345,7 +348,9 @@ module.exports = function (Meta) { /** * otherwise, just clean up --debug/--debug-brk options which are set up by default from the parent one */ - forkProcessParams = { execArgv: [] }; + forkProcessParams = { + execArgv: [], + }; } return forkProcessParams; diff --git a/src/password.js b/src/password.js index d4fd1b0f8d..816e357d12 100644 --- a/src/password.js +++ b/src/password.js @@ -2,6 +2,7 @@ (function (module) { var fork = require('child_process').fork; + var path = require('path'); module.hash = function (rounds, password, callback) { forkChild({ type: 'hash', rounds: rounds, password: password }, callback); @@ -16,7 +17,7 @@ if (global.v8debug || parseInt(process.execArgv.indexOf('--debug'), 10) !== -1) { forkProcessParams = { execArgv: ['--debug=' + (5859), '--nolazy'] }; } - var child = fork('./bcrypt', [], forkProcessParams); + var child = fork(path.join(__dirname, 'bcrypt'), [], forkProcessParams); child.on('message', function (msg) { if (msg.err) { diff --git a/src/reset.js b/src/reset.js index f709c4188b..c3bac3bbbb 100644 --- a/src/reset.js +++ b/src/reset.js @@ -1,5 +1,6 @@ 'use strict'; +var path = require('path'); var winston = require('winston'); var nconf = require('nconf'); var async = require('async'); @@ -83,7 +84,7 @@ function resetTheme(themeId, callback) { var meta = require('./meta'); var fs = require('fs'); - fs.access('node_modules/' + themeId + '/package.json', function (err) { + fs.access(path.join(__dirname, '../node_modules', themeId, 'package.json'), function (err) { if (err) { winston.warn('[reset] Theme `%s` is not installed on this forum', themeId); callback(new Error('theme-not-found')); diff --git a/src/start.js b/src/start.js index 731e939911..2a6e53f507 100644 --- a/src/start.js +++ b/src/start.js @@ -104,7 +104,6 @@ function setupConfigs() { function printStartupInfo() { if (nconf.get('isPrimary') === 'true') { - winston.info('Time: %s', (new Date()).toString()); winston.info('Initializing NodeBB v%s', nconf.get('version')); var host = nconf.get(nconf.get('database') + ':host'); diff --git a/src/topics.js b/src/topics.js index 164200016d..ed7deacc2e 100644 --- a/src/topics.js +++ b/src/topics.js @@ -126,7 +126,7 @@ var social = require('./social'); Topics.getUserBookmarks(tids, uid, next); }, teasers: function (next) { - Topics.getTeasers(topics, next); + Topics.getTeasers(topics, uid, next); }, tags: function (next) { Topics.getTopicsTagsObjects(tids, next); diff --git a/src/topics/posts.js b/src/topics/posts.js index 34e58c55aa..ce2b6dd457 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -63,7 +63,7 @@ module.exports = function (Topics) { var uids = []; postData.forEach(function (postData) { - if (postData && postData[field] && uids.indexOf(postData[field]) === -1) { + if (postData && parseInt(postData[field], 10) >= 0 && uids.indexOf(postData[field]) === -1) { uids.push(postData[field]); } }); diff --git a/src/topics/teaser.js b/src/topics/teaser.js index aa64d7780f..b990b608d1 100644 --- a/src/topics/teaser.js +++ b/src/topics/teaser.js @@ -12,7 +12,11 @@ var plugins = require('../plugins'); var utils = require('../../public/src/utils'); module.exports = function (Topics) { - Topics.getTeasers = function (topics, callback) { + Topics.getTeasers = function (topics, uid, callback) { + if (typeof uid === 'function') { + callback = uid; + uid = 0; + } if (!Array.isArray(topics) || !topics.length) { return callback(null, []); } @@ -94,7 +98,7 @@ module.exports = function (Topics) { return tidToPost[topic.tid]; }); - plugins.fireHook('filter:teasers.get', { teasers: teasers }, next); + plugins.fireHook('filter:teasers.get', { teasers: teasers, uid: uid }, next); }, function (data, next) { next(null, data.teasers); @@ -102,7 +106,11 @@ module.exports = function (Topics) { ], callback); }; - Topics.getTeasersByTids = function (tids, callback) { + Topics.getTeasersByTids = function (tids, uid, callback) { + if (typeof uid === 'function') { + callback = uid; + uid = 0; + } if (!Array.isArray(tids) || !tids.length) { return callback(null, []); } @@ -111,13 +119,17 @@ module.exports = function (Topics) { Topics.getTopicsFields(tids, ['tid', 'postcount', 'teaserPid'], next); }, function (topics, next) { - Topics.getTeasers(topics, next); + Topics.getTeasers(topics, uid, next); }, ], callback); }; - Topics.getTeaser = function (tid, callback) { - Topics.getTeasersByTids([tid], function (err, teasers) { + Topics.getTeaser = function (tid, uid, callback) { + if (typeof uid === 'function') { + callback = uid; + uid = 0; + } + Topics.getTeasersByTids([tid], uid, function (err, teasers) { callback(err, Array.isArray(teasers) && teasers.length ? teasers[0] : null); }); }; diff --git a/src/user/notifications.js b/src/user/notifications.js index 92a65e2649..318794fb20 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -199,20 +199,21 @@ var privileges = require('../privileges'); db.getObjectsFields(keys, ['mergeId'], next); }, - ], function (err, mergeIds) { - // A missing (null) mergeId means that notification is counted separately. - mergeIds = mergeIds.map(function (set) { - return set.mergeId; - }); + function (mergeIds, next) { + mergeIds = mergeIds.map(function (set) { + return set.mergeId; + }); - callback(err, mergeIds.reduce(function (count, cur, idx, arr) { - if (cur === null || idx === arr.indexOf(cur)) { - count += 1; - } + next(null, mergeIds.reduce(function (count, mergeId, idx, arr) { + // A missing (null) mergeId means that notification is counted separately. + if (mergeId === null || idx === arr.indexOf(mergeId)) { + count += 1; + } - return count; - }, 0)); - }); + return count; + }, 0)); + }, + ], callback); }; UserNotifications.getUnreadByField = function (uid, field, values, callback) { diff --git a/src/views/admin/settings/user.tpl b/src/views/admin/settings/user.tpl index c7373fbeb7..adaa4f8010 100644 --- a/src/views/admin/settings/user.tpl +++ b/src/views/admin/settings/user.tpl @@ -167,6 +167,16 @@ +
+ + +
diff --git a/test/translator.js b/test/translator.js index 6411d01992..00822955bc 100644 --- a/test/translator.js +++ b/test/translator.js @@ -156,6 +156,27 @@ describe('new Translator(language)', function () { assert.strictEqual(translated, 'Latest Users'); }); }); + + it('should use key for unknown keys without arguments', function () { + var translator = Translator.create('en-GB'); + return translator.translate('[[unknown:key.without.args]]').then(function (translated) { + assert.strictEqual(translated, 'key.without.args'); + }); + }); + + it('should use backup for unknown keys with arguments', function () { + var translator = Translator.create('en-GB'); + return translator.translate('[[unknown:key.with.args, arguments are here, derpity, derp]]').then(function (translated) { + assert.strictEqual(translated, '[[unknown:key.with.args, arguments are here, derpity, derp[['); + }); + }); + + it('should ignore unclosed tokens', function () { + var translator = Translator.create('en-GB'); + return translator.translate('here is some stuff and other things [[abc:xyz, other random stuff should be fine here [[global:home]] and more things [[pages:users/latest]]').then(function (translated) { + assert.strictEqual(translated, 'here is some stuff and other things [[abc:xyz, other random stuff should be fine here Home and more things Latest Users'); + }); + }); }); });