diff --git a/.gitignore b/.gitignore index 72399b5f92..4be45e03e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +dist/ yarn.lock npm-debug.log node_modules/ diff --git a/.mocharc.yml b/.mocharc.yml new file mode 100644 index 0000000000..16d8518d1b --- /dev/null +++ b/.mocharc.yml @@ -0,0 +1,4 @@ +reporter: dot +timeout: 25000 +exit: true +bail: true diff --git a/.travis.yml b/.travis.yml index 875624ab1e..231d5dbf8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_script: - "mongo mydb_test --eval 'db.createUser({user:\"travis\", pwd: \"test\", roles: []});'" - sh -c "if [ '$DB' = 'mongodb' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"mongo\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":27017,\\\"database\\\":\\\"travis_ci_test\\\"}\"; fi" - sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567/forum\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":6379,\\\"database\\\":1}\"; fi" - - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database nodebb;' -U postgres; psql -c 'create database travis_ci_test;' -U postgres; node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"postgres\\\",\\\"postgres:host\\\":\\\"127.0.0.1\\\",\\\"postgres:port\\\":5432,\\\"postgres:password\\\":\\\"\\\",\\\"postgres:database\\\":\\\"nodebb\\\",\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":5432,\\\"username\\\":\\\"postgres\\\",\\\"database\\\":\\\"travis_ci_test\\\"}\"; fi" + - sh -c "if [ '$DB' = 'postgres' ]; then psql -c 'create database nodebb;' -U postgres; psql -c 'create database travis_ci_test;' -U postgres; node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"postgres\\\",\\\"postgres:host\\\":\\\"127.0.0.1\\\",\\\"postgres:port\\\":5433,\\\"postgres:password\\\":\\\"\\\",\\\"postgres:database\\\":\\\"nodebb\\\",\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"hAN3Eg8W\\\",\\\"admin:password:confirm\\\":\\\"hAN3Eg8W\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":5433,\\\"username\\\":\\\"postgres\\\",\\\"database\\\":\\\"travis_ci_test\\\"}\"; fi" after_success: - "npm run coveralls" language: node_js @@ -23,7 +23,7 @@ dist: xenial env: global: - PGUSER=postgres - - PGPORT=5432 + - PGPORT=5433 - CXX=g++-4.8 jobs: - "DB=mongodb TEST_ENV=production" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be6f7a448..b1af6016e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,99 @@ +#### 1.13.2 (2020-02-05) + +##### Chores + +* incrementing version number - v1.13.2 (71f4607d) +* bump themes (027f3f22) +* bump vanilla (236a1730) +* bump persona (82ace391) +* incrementing version number - v1.13.1 (cc6758a0) +* **deps:** + * update dependency eslint to v6.8.0 (#8062) (15783213) + * update dependency nyc to v15 (#8094) (976e26a9) + * update commitlint monorepo (#8100) (eb4a1a57) + * update dependency lint-staged to v10.0.7 (#8132) (cdfbcbb9) + * update dependency mocha to v7 (#8106) (b370333c) + * update dependency husky to v4 (dd440ce9) + * update dependency lint-staged to v10.0.1 (66992a55) + * update dependency lint-staged to v10 (d74eecfb) + +##### Documentation Changes + +* updated changelog (2edc6960) + +##### New Features + +* add test for isOnline (66febb80) +* add test for change post owner (df2c7851) +* check flag values on save (assignee and state) (#8122) (8e5a2276) + +##### Bug Fixes + +* admin relogin (a5ef6b53) +* #8135 (c35a21d7) +* handle mkdirp0.5->1.0x so it doesn't break upgrade (1e50616c) +* #8134, upgrade mkdirp to 1.0.x (87225a90) +* onSuccessfulLogin not working (111ed802) +* #8139, dont allow restore if not deleted by self (8c48f94b) +* use view_deleted when filtering, closes #8137 (9969dd63) +* escape invalid rules (d927b763) +* add missing await (3cca929a) +* missing await in SocketPosts.changeOwner (0ae1eb4f) +* #8133, check if user is in room before removing (23810cc6) +* add missing await (cd1fa27a) +* missing await (f799f017) +* dont return flag data to client (418c174d) +* check if user has read priv before flagging (51236df4) +* restrict getUsersInRoom to members (1f13ab8a) +* remove unused conditional, dont add dupe messages (3077eb94) +* tests for messaging (ecc579a2) +* #8127 user join system message duplicated (594cd7e1) +* background-size in taskbar images (106c141f) +* tests, was using hardcoded message id (1b08f376) +* typo in #8116 (8bb5e71e) +* build step defaults to series instead of parallel (3fac09b1) +* escape system message, don't allow editing system messages (6a63c1a1) +* escape register query param (c8fb7f92) +* delete upload (8c6a7954) +* check uploadName (153b1a0e) +* #8120, bubble errors from static hooks (01d1ae78) +* escape bootswatchSkin and homepageRoute (b0f3e48a) +* change owner missing await (3e525576) +* hsts always enabled (e3952674) +* escape topic.thumb (b7a57996) +* #8112, don't crash hook returns no data (4eb9652a) +* escape config.userLang/acpLang, don't allow invalid language codes (e06c1bfc) +* group create/join/update name validation (61da8c29) +* don't crash if groupData is missing (48f08627) +* #8105, fix export json on page load (5a8217de) +* #8103, fix advanced menu not displaying in ACP (52774531) +* meta description missing if url doesn't have post index (10989ccc) +* create user modal instantly closing (c1b1ee61) +* login with weak password (9d074731) +* dont check password strength on login (f6d7a24a) +* **deps:** + * update dependency connect-redis to v4.0.4 (#8143) (16ab641d) + * update dependency rimraf to v3.0.1 (#8138) (726ba71c) + * update dependency validator to v12.2.0 (#8136) (f07b4bfa) + * update dependency nodebb-theme-persona to v10.1.34 (#8140) (6d7131fb) + * update dependency nodebb-theme-persona to v10.1.31 (#8129) (c510a2c4) + * update dependency mongodb to v3.5.2 (#8092) (0e49cfb9) + * update dependency sharp to v0.24.0 (#8121) (16e8f496) + * update dependency nodebb-plugin-composer-default to v6.3.21 (#8119) (ca10f8f0) + * update dependency nodebb-widget-essentials to v4.0.18 (#8111) (df5e3a73) + +##### Other Changes + +* NodeBB/NodeBB (b959c24a) +* //github.com/NodeBB/NodeBB (ee4304b4) +* //github.com/NodeBB/NodeBB (bfaba895) +* save disableLeave (#8123) (09d55581) +* //github.com/NodeBB/NodeBB (842916ea) + +##### Refactors + +* messaging (30c50361) + #### 1.13.1 (2019-12-19) ##### Chores diff --git a/Dockerfile b/Dockerfile index 56c2ad7497..28d8586a48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,5 +19,4 @@ ENV NODE_ENV=production \ EXPOSE 4567 -CMD ./nodebb start - +CMD node ./nodebb build ; node ./nodebb start diff --git a/install/data/defaults.json b/install/data/defaults.json index a6f53f49c0..86fc1075c4 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -109,6 +109,7 @@ "email:sendmail:rateLimit": 2, "email:sendmail:rateDelta": 1000, "hideFullname": 0, + "hideEmail": 0, "allowGuestHandles": 0, "disableRecentCategoryFilter": 0, "maximumRelatedTopics": 0, diff --git a/install/package.json b/install/package.json index fe110faf29..b42a555ad6 100644 --- a/install/package.json +++ b/install/package.json @@ -29,123 +29,125 @@ ] }, "dependencies": { - "ace-builds": "^1.2.9", - "archiver": "^3.0.0", - "async": "^3.0.1", - "autoprefixer": "^9.4.6", + "ace-builds": "^1.4.9", + "archiver": "^4.0.0", + "async": "^3.2.0", + "autoprefixer": "^9.7.6", "bcryptjs": "2.4.3", - "benchpressjs": "^2.0.0", - "body-parser": "^1.18.2", - "bootstrap": "^3.4.0", + "benchpressjs": "^2.0.2", + "body-parser": "^1.19.0", + "bootstrap": "^3.4.1", "bootswatch": "git://github.com/thomaspark/bootswatch.git#c41a8f066feb8950c6f9c6bcf5a3c37d1085404e", - "chart.js": "^2.7.1", + "chart.js": "^2.9.3", "cli-graph": "^3.2.2", - "clipboard": "^2.0.1", - "colors": "^1.1.2", - "commander": "^3.0.0", - "compression": "^1.7.1", + "clipboard": "^2.0.6", + "colors": "^1.4.0", + "commander": "^5.0.0", + "compression": "^1.7.4", "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", "connect-mongo": "3.2.0", - "connect-multiparty": "^2.1.0", - "connect-pg-simple": "^6.0.0", + "connect-multiparty": "^2.2.0", + "connect-pg-simple": "^6.1.0", "connect-redis": "4.0.4", - "cookie-parser": "^1.4.3", - "cron": "^1.3.0", - "cropperjs": "^1.2.2", - "csurf": "^1.9.0", + "cookie-parser": "^1.4.5", + "cron": "^1.8.2", + "cropperjs": "^1.5.6", + "csurf": "^1.11.0", "daemon": "^1.1.0", - "diff": "^4.0.1", - "express": "^4.16.2", - "express-session": "^1.15.6", - "express-useragent": "^1.0.12", - "graceful-fs": "^4.1.11", - "helmet": "^3.11.0", - "html-to-text": "^5.0.0", - "ipaddr.js": "^1.5.4", - "jquery": "3.4.1", - "jsesc": "2.5.2", - "json-2-csv": "^3.0.0", - "jsonwebtoken": "^8.4.0", - "less": "^3.10.3", + "diff": "^4.0.2", + "express": "^4.17.1", + "express-session": "^1.17.0", + "express-useragent": "^1.0.13", + "graceful-fs": "^4.2.3", + "helmet": "^3.22.0", + "html-to-text": "^5.1.1", + "ipaddr.js": "^1.9.1", + "jquery": "3.5.1", + "jsesc": "3.0.1", + "json-2-csv": "^3.6.2", + "jsonwebtoken": "^8.5.1", + "less": "^3.11.1", "lodash": "^4.17.15", - "logrotate-stream": "^0.2.5", + "logrotate-stream": "^0.2.6", "lru-cache": "5.1.1", "material-design-lite": "^1.3.0", - "mime": "^2.2.0", - "mkdirp": "^1.0.3", - "mongodb": "3.5.2", - "morgan": "^1.9.1", - "mousetrap": "^1.6.1", - "mubsub-nbb": "^1.5.1", + "mime": "^2.4.4", + "mkdirp": "^1.0.4", + "mongodb": "3.5.7", + "morgan": "^1.10.0", + "mousetrap": "^1.6.5", + "@nodebb/mubsub": "^1.6.0", "nconf": "^0.10.0", - "nodebb-plugin-composer-default": "6.3.21", + "nodebb-plugin-composer-default": "6.3.25", "nodebb-plugin-dbsearch": "4.0.7", - "nodebb-plugin-emoji": "^3.0.0", + "nodebb-plugin-emoji": "^3.3.0", "nodebb-plugin-emoji-android": "2.0.0", - "nodebb-plugin-markdown": "8.11.0", + "nodebb-plugin-markdown": "8.11.2", "nodebb-plugin-mentions": "2.7.4", "nodebb-plugin-soundpack-default": "1.0.0", "nodebb-plugin-spam-be-gone": "0.6.7", - "nodebb-rewards-essentials": "0.1.2", + "nodebb-rewards-essentials": "0.1.3", "nodebb-theme-lavender": "5.0.11", - "nodebb-theme-persona": "10.1.34", - "nodebb-theme-slick": "1.2.28", - "nodebb-theme-vanilla": "11.1.15", - "nodebb-widget-essentials": "4.0.18", - "nodemailer": "^6.0.0", - "passport": "^0.4.0", + "nodebb-theme-persona": "10.1.39", + "nodebb-theme-slick": "1.2.29", + "nodebb-theme-vanilla": "11.1.16", + "nodebb-widget-essentials": "4.1.0", + "nodemailer": "^6.4.6", + "passport": "^0.4.1", "passport-local": "1.0.0", - "pg": "^7.4.0", - "pg-cursor": "^2.0.0", - "postcss": "7.0.26", + "pg": "^8.0.2", + "pg-cursor": "^2.1.9", + "postcss": "7.0.27", "postcss-clean": "1.1.0", - "promise-polyfill": "^8.0.0", + "promise-polyfill": "^8.1.3", "prompt": "^1.0.0", - "redis": "2.8.0", - "request": "2.88.0", - "rimraf": "3.0.1", + "redis": "3.0.2", + "request": "2.88.2", + "request-promise-native": "^1.0.8", + "rimraf": "3.0.2", "rss": "^1.2.2", - "sanitize-html": "^1.16.3", - "semver": "^7.0.0", - "serve-favicon": "^2.4.5", - "sharp": "0.24.0", - "sitemap": "^5.0.0", + "sanitize-html": "^1.23.0", + "semver": "^7.2.1", + "serve-favicon": "^2.5.0", + "sharp": "0.25.2", + "sitemap": "^6.1.0", "socket.io": "2.3.0", "socket.io-adapter-cluster": "^1.0.1", - "socket.io-adapter-mongo": "^2.0.4", + "socket.io-adapter-mongo": "^2.0.5", "socket.io-adapter-postgres": "^1.2.1", "socket.io-client": "2.3.0", "socket.io-redis": "5.2.0", "socketio-wildcard": "2.0.0", - "spdx-license-list": "^6.0.0", + "spdx-license-list": "^6.1.0", "spider-detector": "2.0.0", "textcomplete": "^0.17.1", "textcomplete.contenteditable": "^0.1.1", "toobusy-js": "^0.5.1", "uglify-es": "^3.3.9", - "validator": "12.2.0", + "validator": "13.0.0", "winston": "3.2.1", "xml": "^1.0.1", - "xregexp": "^4.1.1", + "xregexp": "^4.3.0", "zxcvbn": "^4.4.2" }, "devDependencies": { + "@apidevtools/swagger-parser": "9.0.1", "@commitlint/cli": "8.3.5", "@commitlint/config-angular": "8.3.4", - "coveralls": "3.0.9", + "coveralls": "3.1.0", "eslint": "6.8.0", - "eslint-config-airbnb-base": "14.0.0", - "eslint-plugin-import": "2.18.2", - "grunt": "1.0.4", + "eslint-config-airbnb-base": "14.1.0", + "eslint-plugin-import": "2.20.2", + "grunt": "1.1.0", "grunt-contrib-watch": "1.1.0", - "husky": "4.2.1", - "jsdom": "15.2.1", - "lint-staged": "10.0.7", - "mocha": "7.0.1", + "husky": "4.2.5", + "jsdom": "16.2.2", + "lint-staged": "10.2.0", + "mocha": "7.1.2", "mocha-lcov-reporter": "1.3.0", - "nyc": "15.0.0", - "smtp-server": "3.5.0" + "nyc": "15.0.1", + "smtp-server": "3.6.0" }, "bugs": { "url": "https://github.com/NodeBB/NodeBB/issues" diff --git a/loader.js b/loader.js index 6320c6ab6a..eea0bf9c8a 100644 --- a/loader.js +++ b/loader.js @@ -231,7 +231,7 @@ fs.open(pathToConfig, 'r', function (err) { cwd: process.cwd(), }); - fs.writeFileSync(pidFilePath, process.pid); + fs.writeFileSync(pidFilePath, String(process.pid)); } async.series([ diff --git a/public/language/ar/admin/development/info.json b/public/language/ar/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/ar/admin/development/info.json +++ b/public/language/ar/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/ar/admin/manage/tags.json b/public/language/ar/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/ar/admin/manage/tags.json +++ b/public/language/ar/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/ar/admin/settings/advanced.json b/public/language/ar/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/ar/admin/settings/advanced.json +++ b/public/language/ar/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/ar/admin/settings/general.json b/public/language/ar/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/ar/admin/settings/general.json +++ b/public/language/ar/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/ar/admin/settings/group.json b/public/language/ar/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/ar/admin/settings/group.json +++ b/public/language/ar/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/ar/admin/settings/user.json b/public/language/ar/admin/settings/user.json index deec63d64b..b433a32fd2 100644 --- a/public/language/ar/admin/settings/user.json +++ b/public/language/ar/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "البريد الالكتروني فقط", "account-settings": "إعدادت الحساب", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "عدم السماح بتغيير اسم المستخدم", "disable-email-changes": "عدم السماح بتغيير البريد الالكتروني", "disable-password-changes": "عدم السماح بتغيير كلمة المرور", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json index ffb6c7747c..505b331427 100644 --- a/public/language/ar/modules.json +++ b/public/language/ar/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "آخر الدردشات", "chat.contacts": "الأصدقاء", "chat.message-history": "تاريخ الرسائل", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "افتح الدردشة في نافذة خاصة", "chat.minimize": "Minimize", diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json index 8b63fa0601..f804cb85d6 100644 --- a/public/language/ar/topic.json +++ b/public/language/ar/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "أدخل عنوان موضوعك هنا...", - "composer.handle_placeholder": "اﻹسم", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "نبذ التغييرات", "composer.submit": "حفظ", "composer.replying_to": "الرد على %1", diff --git a/public/language/bg/admin/development/info.json b/public/language/bg/admin/development/info.json index a1fe782b9f..15c75e847b 100644 --- a/public/language/bg/admin/development/info.json +++ b/public/language/bg/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Информация — Вие сте на %1:%2", + "you-are-on": "Вие сте на %1:%2", + "ip": "IP %1", "nodes-responded": "%1 възела отговориха в рамките на %2мсек!", "host": "сървър", "pid": "ид. на процеса", diff --git a/public/language/bg/admin/manage/tags.json b/public/language/bg/admin/manage/tags.json index b9131ee925..0acb6bd42d 100644 --- a/public/language/bg/admin/manage/tags.json +++ b/public/language/bg/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Натиснете тук, за да отворите страницата с настройки на етикета.", "name": "Име на етикета", - "alerts.editing-multiple": "Редактиране на множество етикети", - "alerts.editing-x": "Редактиране на етикета „%1“", + "alerts.editing": "Редактиране на етикет(и)", "alerts.confirm-delete": "Наистина ли искате да изтриете избраните етикети?", "alerts.update-success": "Етикетът е променен!" } \ No newline at end of file diff --git a/public/language/bg/admin/settings/advanced.json b/public/language/bg/admin/settings/advanced.json index acaea9d1c2..de210085d3 100644 --- a/public/language/bg/admin/settings/advanced.json +++ b/public/language/bg/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Заглавки за разрешаване на управлението на достъпа", "hsts": "Стриктна транспортна сигурност", "hsts.enabled": "Включване на HSTS (препоръчително)", + "hsts.maxAge": "Максимална възраст на HSTS", "hsts.subdomains": "Включване на поддомейните в заглавката на HSTS", "hsts.preload": "Позволяване на предварителното зареждане на заглавката на HSTS", "hsts.help": "Ако това е включено, за този уеб ще бъде настроена заглавка за HSTS. Можете да изберете дали да включите поддомейните и дали за заредите предварително флаговете в заглавката си. Ако не знаете какво да направите, най-добре не избирайте нищо. Още информация", diff --git a/public/language/bg/admin/settings/general.json b/public/language/bg/admin/settings/general.json index 64e5df51d8..04b4b51d67 100644 --- a/public/language/bg/admin/settings/general.json +++ b/public/language/bg/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Настройки на уеб сайта", "title": "Заглавие на уеб сайта", + "title.short": "Кратко заглавие", + "title.short-placeholder": "Ако не е посочено кратко заглавие, ще бъде използвано заглавието на уеб сайта", "title.url": "Адрес", "title.url-placeholder": "Адресът на заглавието на уеб сайта", "title.url-help": "При щракване върху заглавието, потребителите ще бъдат изпратени на този адрес. Ако бъде оставено празно, потребителите ще бъдат изпращани на началната страница на форума.", @@ -31,5 +33,9 @@ "outgoing-links": "Изходящи връзки", "outgoing-links.warning-page": "Показване на предупредителна страница при щракване върху външни връзки", "search-default-sort-by": "Подредба по подразбиране при търсене", - "outgoing-links.whitelist": "Домейни, за които да не се показва предупредителната страница" -} \ No newline at end of file + "outgoing-links.whitelist": "Домейни, за които да не се показва предупредителната страница", + "site-colors": "Мета-данни за цвета на уеб сайта", + "theme-color": "Цвят на темата", + "background-color": "Фонов цвят", + "background-color-help": "Цвят, който да се използва като фон за началния екран, когато уеб сайтът е инсталиран като приложение" +} diff --git a/public/language/bg/admin/settings/group.json b/public/language/bg/admin/settings/group.json index c514b417af..15a08b2c80 100644 --- a/public/language/bg/admin/settings/group.json +++ b/public/language/bg/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Частни групи", "private-groups.help": "Ако е включено, присъединяването към групи ще изисква одобрение от собственик на групата. (По подразбиране: включено)", "private-groups.warning": "Внимание! Ако това е изключено и имате частни групи, те автоматично ще станат публични.", + "allow-multiple-badges": "Позволяване на множество значки", "allow-multiple-badges-help": "Това може да се използва, за да позволи на потребителите да избират множество значки за групите. Изисква поддържа на теми.", "max-name-length": "Минимална дължина на името на група", "max-title-length": "Максимална дължина на заглавието на група", diff --git a/public/language/bg/admin/settings/user.json b/public/language/bg/admin/settings/user.json index fcda182db0..a07cfcdee4 100644 --- a/public/language/bg/admin/settings/user.json +++ b/public/language/bg/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Само е-поща", "account-settings": "Настройки на акаунта", "gdpr_enabled": "Включване на искането за съгласие с ОРЗД", - "gdpr_enabled_help": "Ако това е включено, всички новорегистрирани потребители ще бъдат задължени изрично да дадат съгласието си за събирането на данни и статистики за потреблението според Общия регламент относно защитата на данните (ОРЗД). Забележка: Включването на ОРЗД не задължава съществуващите потребители да дадат съгласието си. Ако искате това, ще трябва да инсталирате добавката за ОРЗД (GDPR).", + "gdpr_enabled_help": "Ако това е включено, всички новорегистрирани потребители ще бъдат задължени изрично да дадат съгласието си за събирането на данни и статистики за потреблението според Общия регламент относно защитата на данните (ОРЗД). Забележка: Включването на ОРЗД не задължава съществуващите потребители да дадат съгласието си. Ако искате това, ще трябва да инсталирате добавката за ОРЗД (GDPR).", "disable-username-changes": "Забраняване на промяната на потребителското име", "disable-email-changes": "Забраняване на промяната на е-пощата", "disable-password-changes": "Забраняване на промяната на паролата", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Да се следят", "categoryWatchState.notwatching": "Да не се следят", "categoryWatchState.ignoring": "Да се пренебрегват" -} \ No newline at end of file +} diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index 4861a5b113..88842d859c 100644 --- a/public/language/bg/modules.json +++ b/public/language/bg/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Скорошни разговори", "chat.contacts": "Контакти", "chat.message-history": "История на съобщенията", + "chat.message-deleted": "Съобщението е изтрито", "chat.options": "Настройки на разговора", "chat.pop-out": "Отделяне на разговора в прозорец", "chat.minimize": "Намаляване", diff --git a/public/language/bg/topic.json b/public/language/bg/topic.json index a6c93b59b0..4820d8052e 100644 --- a/public/language/bg/topic.json +++ b/public/language/bg/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Натиснете публикациите, които искате да преместите", "change_owner_instruction": "Натиснете публикациите, които искате да прехвърлите на друг потребител", "composer.title_placeholder": "Въведете заглавието на темата си тук...", - "composer.handle_placeholder": "Име", + "composer.handle_placeholder": "Въведете името тук", "composer.discard": "Отхвърляне", "composer.submit": "Публикуване", "composer.replying_to": "Отговор на %1", diff --git a/public/language/bn/admin/development/info.json b/public/language/bn/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/bn/admin/development/info.json +++ b/public/language/bn/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/bn/admin/manage/tags.json b/public/language/bn/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/bn/admin/manage/tags.json +++ b/public/language/bn/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/bn/admin/settings/advanced.json b/public/language/bn/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/bn/admin/settings/advanced.json +++ b/public/language/bn/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/bn/admin/settings/general.json b/public/language/bn/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/bn/admin/settings/general.json +++ b/public/language/bn/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/bn/admin/settings/group.json b/public/language/bn/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/bn/admin/settings/group.json +++ b/public/language/bn/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/bn/admin/settings/user.json b/public/language/bn/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/bn/admin/settings/user.json +++ b/public/language/bn/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/bn/modules.json b/public/language/bn/modules.json index f1e23c4370..22c8ce7969 100644 --- a/public/language/bn/modules.json +++ b/public/language/bn/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "সাম্প্রতিক চ্যাটসমূহ", "chat.contacts": "কন্টাক্টস", "chat.message-history": "মেসেজ হিস্টোরী", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "চ্যাট উইন্ডো আলাদা করুন", "chat.minimize": "Minimize", diff --git a/public/language/bn/topic.json b/public/language/bn/topic.json index ae16047e44..0c7c6d77a7 100644 --- a/public/language/bn/topic.json +++ b/public/language/bn/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "আপনার টপিকের শিরোনাম দিন", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "বাতিল", "composer.submit": "সাবমিট", "composer.replying_to": "%1 এর উত্তরে:", diff --git a/public/language/cs/admin/development/info.json b/public/language/cs/admin/development/info.json index 363c074a01..026379c06a 100644 --- a/public/language/cs/admin/development/info.json +++ b/public/language/cs/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Informace – jste na %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 vazeb odpovědělo během %2ms.", "host": "host", "pid": "pid", diff --git a/public/language/cs/admin/manage/tags.json b/public/language/cs/admin/manage/tags.json index 3a2e5682e2..5d957e78ec 100644 --- a/public/language/cs/admin/manage/tags.json +++ b/public/language/cs/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Pro přejití na stránku s nastavením značek, klikněte zde.", "name": "Název značky", - "alerts.editing-multiple": "Upravení víceznaček", - "alerts.editing-x": "Upravení značky \"%1\"", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Chcete odstranit vybranou značku?", "alerts.update-success": "Značka aktualizována." } \ No newline at end of file diff --git a/public/language/cs/admin/settings/advanced.json b/public/language/cs/admin/settings/advanced.json index ccc9175270..75f08573c2 100644 --- a/public/language/cs/admin/settings/advanced.json +++ b/public/language/cs/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Přísné zabezpečení přenosu", "hsts.enabled": "Povolit HSTS (doporučeno)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Zahrnout poddomény v hlavičce HSTS", "hsts.preload": "Povolit před-načtení hlavičky HSTS", "hsts.help": "Je-li povoleno, bude nastavena pro tyto stránky hlavička HSTS . Můžete si v hlavičce zvolit zahrnutí i poddomén a přednastavených příznaků. Nejste-li si jist/a, ponechte nezaškrtnutéVíce informací ", diff --git a/public/language/cs/admin/settings/general.json b/public/language/cs/admin/settings/general.json index a57ec12acb..99b4781437 100644 --- a/public/language/cs/admin/settings/general.json +++ b/public/language/cs/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Nastavení stránky", "title": "Název stránky", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "URL názvu stránky", "title.url-help": "Bude-li kliknuto na název, uživatel bude přesměrován na tuto adresu. Zůstane-li prázdné, uživatel bude odeslán na index fóra", @@ -31,5 +33,9 @@ "outgoing-links": "Odchozí odkazy", "outgoing-links.warning-page": "Použít stránku s upozorněním při odchozích odkazech", "search-default-sort-by": "Výchozí třídění při hledání", - "outgoing-links.whitelist": "Domény u kterých bude přeskočena upozorňovací stránka" -} \ No newline at end of file + "outgoing-links.whitelist": "Domény u kterých bude přeskočena upozorňovací stránka", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/cs/admin/settings/group.json b/public/language/cs/admin/settings/group.json index d27e18fd69..066361fae7 100644 --- a/public/language/cs/admin/settings/group.json +++ b/public/language/cs/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Soukromé skupiny", "private-groups.help": "Je-li povoleno, připojení ke skupině vyžaduje schválení zakladatele skupiny (výchozí: povoleno)", "private-groups.warning": "Ale pozor, je-li tato možnost zakázána a vy máte soukromé skupiny, stanou se automaticky veřejnými.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Toto označení může být použito, aby uživatelé mohly vybrat několik skupinových symbolů, vyžaduje podporu motivu.", "max-name-length": "Maximální délka názvu skupiny", "max-title-length": "Maximální délka názvu skupiny", diff --git a/public/language/cs/admin/settings/user.json b/public/language/cs/admin/settings/user.json index 22741f9de6..198c69b296 100644 --- a/public/language/cs/admin/settings/user.json +++ b/public/language/cs/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Pouze e-mail", "account-settings": "Nastavení účtu", "gdpr_enabled": "Povolit souhlas s GDPR", - "gdpr_enabled_help": "Je-li povoleno, všichni nový uživatelé budou muset souhlasit se sběrem dat dle General Data Protection Regulation (GDPR).Nezapomeňte, že: povolení GDPR nepřinutí již existující uživatele dát souhlas. Abyste tak učinili, bude muset nainstalovat zásuvný modul GDPR.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Zakázat změnu uživatelského jména", "disable-email-changes": "Zakázat změnu e-mailu", "disable-password-changes": "Zakázat změnu hesla", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Sledování", "categoryWatchState.notwatching": "Nesleduji", "categoryWatchState.ignoring": "Ignorace" -} \ No newline at end of file +} diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index d4db8f3f5a..7c59630e9e 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Aktuální konverzace", "chat.contacts": "Kontakty", "chat.message-history": "Historie zpráv", + "chat.message-deleted": "Message Deleted", "chat.options": "Možnosti konverzace", "chat.pop-out": "Skrýt konverzaci", "chat.minimize": "Minimalizovat", diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index 072b2c4a2f..39aec20a52 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Klikněte na příspěvek/y, který chcete přesunout", "change_owner_instruction": "Klikněte na příspěvek u kterého chcete změnit vlastníka", "composer.title_placeholder": "Zadejte název tématu…", - "composer.handle_placeholder": "Jméno", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Zrušit", "composer.submit": "Odeslat", "composer.replying_to": "Odpovídání na %1", diff --git a/public/language/da/admin/development/info.json b/public/language/da/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/da/admin/development/info.json +++ b/public/language/da/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/da/admin/manage/tags.json b/public/language/da/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/da/admin/manage/tags.json +++ b/public/language/da/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/da/admin/settings/advanced.json b/public/language/da/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/da/admin/settings/advanced.json +++ b/public/language/da/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/da/admin/settings/general.json b/public/language/da/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/da/admin/settings/general.json +++ b/public/language/da/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/da/admin/settings/group.json b/public/language/da/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/da/admin/settings/group.json +++ b/public/language/da/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/da/admin/settings/user.json b/public/language/da/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/da/admin/settings/user.json +++ b/public/language/da/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/da/modules.json b/public/language/da/modules.json index fa008284f7..b2b91815bd 100644 --- a/public/language/da/modules.json +++ b/public/language/da/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Seneste chats", "chat.contacts": "Kontakter", "chat.message-history": "Beskedhistorik", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop ud chatten", "chat.minimize": "Minimize", diff --git a/public/language/da/topic.json b/public/language/da/topic.json index ee3169c59a..92e673c9f2 100644 --- a/public/language/da/topic.json +++ b/public/language/da/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Angiv din trådtittel her ...", - "composer.handle_placeholder": "Navn", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Fortryd", "composer.submit": "Send", "composer.replying_to": "Svare til %1", diff --git a/public/language/de/admin/development/info.json b/public/language/de/admin/development/info.json index a21a412764..8e8a4a30d6 100644 --- a/public/language/de/admin/development/info.json +++ b/public/language/de/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - Sie verwenden %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 Knoten antworteten innerhalb von %2ms", "host": "Host", "pid": "PID", diff --git a/public/language/de/admin/manage/tags.json b/public/language/de/admin/manage/tags.json index 490b424c40..de8e927fac 100644 --- a/public/language/de/admin/manage/tags.json +++ b/public/language/de/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Klicke hier, um die Tag-Einstellungsseite zu öffnen.", "name": "Tagname", - "alerts.editing-multiple": "Bearbeite mehrere Tags", - "alerts.editing-x": "Bearbeite Tag \"%1\"", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Wollen Sie die ausgewählten Tags löschen?", "alerts.update-success": "Tag aktualisiert!" } \ 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 0b984dc710..b89d46d895 100644 --- a/public/language/de/admin/settings/advanced.json +++ b/public/language/de/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "HSTS Aktivieren (empfohlen)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Subdomains in HSTS Header einbinden", "hsts.preload": "Vorabladen von HSTS Header erlauben", "hsts.help": "Wenn aktiviert, wird ein HSTS-Header für diese Seite gesetzt. Du kannst wählen, ob du Subdomains und Preloading-Flags in deinen Header aufnehmen möchtest. Im Zweifelsfall kannst du diese unmarkiert lassen.", diff --git a/public/language/de/admin/settings/general.json b/public/language/de/admin/settings/general.json index de590afb18..80abe8030b 100644 --- a/public/language/de/admin/settings/general.json +++ b/public/language/de/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Forum Einstellungen", "title": "Forum Titel", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "Die URL des Seitentitels", "title.url-help": "Wenn der Titel angeklickt wird, werden Benutzer zu dieser Adresse geschickt, bei leerem Feld wird die Startseite verwendet.", @@ -31,5 +33,9 @@ "outgoing-links": "Ausgehende Links", "outgoing-links.warning-page": "Warnseite für ausgehende links verwenden", "search-default-sort-by": "Standardmäßige Such-Sortierung", - "outgoing-links.whitelist": "Domains, für die keine Warnseite angezeigt werden soll" -} \ No newline at end of file + "outgoing-links.whitelist": "Domains, für die keine Warnseite angezeigt werden soll", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/de/admin/settings/group.json b/public/language/de/admin/settings/group.json index 8ea19fd505..5c9da614a7 100644 --- a/public/language/de/admin/settings/group.json +++ b/public/language/de/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Gruppen", "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-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Diese Eintellung kann verwendet werden um Benutzern zu erlauben mehrere Gruppen abzeichen auszuwählen, benötigt Theme unterstützung.", "max-name-length": "Maximale Länge von Gruppennamen", "max-title-length": "Maximale Gruppentitellänge", diff --git a/public/language/de/admin/settings/user.json b/public/language/de/admin/settings/user.json index 93b4f9a6eb..a359e28796 100644 --- a/public/language/de/admin/settings/user.json +++ b/public/language/de/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Nur E-Mail", "account-settings": "Kontoeinstellungen", "gdpr_enabled": "Aktivieren Sie die DSGVO-Zustimmungserfassung", - "gdpr_enabled_help": "Wenn aktiviert, müssen alle Registranten explizit der Datensammlung und -nutzung unter der Datenschutzgrundverordnung (DSGVO) zustimmen. Hinweis: Die Aktivierung der DSGVO verpflichtet bereits registrierte Nutzer nicht der Zustimmung. Um dies zu tun, musst du das GDPR Plugin installieren.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Deaktiviere Änderungen des Benutzernames", "disable-email-changes": "Deaktiviere Änderungen der E-Mail Adresse", "disable-password-changes": "Deaktiviere Änderungen des Passwortes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Beobachtet", "categoryWatchState.notwatching": "Nicht beobachtet", "categoryWatchState.ignoring": "Ignoriert" -} \ No newline at end of file +} diff --git a/public/language/de/modules.json b/public/language/de/modules.json index 5aa8a7547a..7fb7552547 100644 --- a/public/language/de/modules.json +++ b/public/language/de/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Aktuelle Chats", "chat.contacts": "Kontakte", "chat.message-history": "Nachrichtenverlauf", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat-Optionen", "chat.pop-out": "Chat als Pop-out anzeigen", "chat.minimize": "Minimieren", diff --git a/public/language/de/topic.json b/public/language/de/topic.json index 7b651fd2d8..625591ad4a 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Klicke auf die Beiträge, die du verschieben möchstest.", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Hier den Titel des Themas eingeben...", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Verwerfen", "composer.submit": "Absenden", "composer.replying_to": "Antworte auf %1", diff --git a/public/language/el/admin/development/info.json b/public/language/el/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/el/admin/development/info.json +++ b/public/language/el/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/el/admin/manage/tags.json b/public/language/el/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/el/admin/manage/tags.json +++ b/public/language/el/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/el/admin/settings/advanced.json b/public/language/el/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/el/admin/settings/advanced.json +++ b/public/language/el/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/el/admin/settings/general.json b/public/language/el/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/el/admin/settings/general.json +++ b/public/language/el/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/el/admin/settings/group.json b/public/language/el/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/el/admin/settings/group.json +++ b/public/language/el/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/el/admin/settings/user.json b/public/language/el/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/el/admin/settings/user.json +++ b/public/language/el/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/el/modules.json b/public/language/el/modules.json index 2455a0e417..846dad7d63 100644 --- a/public/language/el/modules.json +++ b/public/language/el/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Recent Chats", "chat.contacts": "Contacts", "chat.message-history": "Message History", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out chat", "chat.minimize": "Minimize", diff --git a/public/language/el/topic.json b/public/language/el/topic.json index 7c71b75d2e..115cff610f 100644 --- a/public/language/el/topic.json +++ b/public/language/el/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Εισαγωγή του τίτλου του θέματος εδώ...", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Πέταγμα", "composer.submit": "Υποβολή", "composer.replying_to": "Απάντηση στο %1", diff --git a/public/language/en-GB/admin/development/info.json b/public/language/en-GB/admin/development/info.json index c095a9718b..da7e6b547a 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", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/en-GB/admin/manage/tags.json b/public/language/en-GB/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/en-GB/admin/manage/tags.json +++ b/public/language/en-GB/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/en-GB/admin/settings/advanced.json b/public/language/en-GB/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/en-GB/admin/settings/advanced.json +++ b/public/language/en-GB/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/en-GB/admin/settings/general.json b/public/language/en-GB/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/en-GB/admin/settings/general.json +++ b/public/language/en-GB/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/en-GB/admin/settings/group.json b/public/language/en-GB/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/en-GB/admin/settings/group.json +++ b/public/language/en-GB/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/en-GB/admin/settings/user.json b/public/language/en-GB/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/en-GB/admin/settings/user.json +++ b/public/language/en-GB/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/en-GB/modules.json b/public/language/en-GB/modules.json index c242b12119..a0f3dfe641 100644 --- a/public/language/en-GB/modules.json +++ b/public/language/en-GB/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Recent Chats", "chat.contacts": "Contacts", "chat.message-history": "Message History", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out chat", "chat.minimize": "Minimize", diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index d4a547d6f8..5fb113f8d4 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -120,7 +120,7 @@ "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Enter your topic title here...", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Discard", "composer.submit": "Submit", "composer.replying_to": "Replying to %1", diff --git a/public/language/en-US/admin/development/info.json b/public/language/en-US/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/en-US/admin/development/info.json +++ b/public/language/en-US/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/en-US/admin/manage/tags.json b/public/language/en-US/admin/manage/tags.json index d265367756..c3eacf5434 100644 --- a/public/language/en-US/admin/manage/tags.json +++ b/public/language/en-US/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/en-US/admin/settings/advanced.json b/public/language/en-US/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/en-US/admin/settings/advanced.json +++ b/public/language/en-US/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/en-US/admin/settings/general.json b/public/language/en-US/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/en-US/admin/settings/general.json +++ b/public/language/en-US/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/en-US/admin/settings/group.json b/public/language/en-US/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/en-US/admin/settings/group.json +++ b/public/language/en-US/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/en-US/admin/settings/user.json b/public/language/en-US/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/en-US/admin/settings/user.json +++ b/public/language/en-US/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/en-US/modules.json b/public/language/en-US/modules.json index 2455a0e417..846dad7d63 100644 --- a/public/language/en-US/modules.json +++ b/public/language/en-US/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Recent Chats", "chat.contacts": "Contacts", "chat.message-history": "Message History", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out chat", "chat.minimize": "Minimize", diff --git a/public/language/en-US/topic.json b/public/language/en-US/topic.json index c4606cbb41..100027b6eb 100644 --- a/public/language/en-US/topic.json +++ b/public/language/en-US/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Enter your topic title here...", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Discard", "composer.submit": "Submit", "composer.replying_to": "Replying to %1", diff --git a/public/language/en-x-pirate/admin/development/info.json b/public/language/en-x-pirate/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/en-x-pirate/admin/development/info.json +++ b/public/language/en-x-pirate/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/en-x-pirate/admin/manage/tags.json b/public/language/en-x-pirate/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/en-x-pirate/admin/manage/tags.json +++ b/public/language/en-x-pirate/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/en-x-pirate/admin/settings/advanced.json b/public/language/en-x-pirate/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/en-x-pirate/admin/settings/advanced.json +++ b/public/language/en-x-pirate/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/en-x-pirate/admin/settings/general.json b/public/language/en-x-pirate/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/en-x-pirate/admin/settings/general.json +++ b/public/language/en-x-pirate/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/en-x-pirate/admin/settings/group.json b/public/language/en-x-pirate/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/en-x-pirate/admin/settings/group.json +++ b/public/language/en-x-pirate/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/en-x-pirate/admin/settings/user.json b/public/language/en-x-pirate/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/en-x-pirate/admin/settings/user.json +++ b/public/language/en-x-pirate/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ 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 78da718248..e3be1bd8fd 100644 --- a/public/language/en-x-pirate/modules.json +++ b/public/language/en-x-pirate/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Recent Chats", "chat.contacts": "Contacts", "chat.message-history": "Message History", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out chat", "chat.minimize": "Minimize", diff --git a/public/language/en-x-pirate/topic.json b/public/language/en-x-pirate/topic.json index c4606cbb41..100027b6eb 100644 --- a/public/language/en-x-pirate/topic.json +++ b/public/language/en-x-pirate/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Enter your topic title here...", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Discard", "composer.submit": "Submit", "composer.replying_to": "Replying to %1", diff --git a/public/language/es/admin/advanced/database.json b/public/language/es/admin/advanced/database.json index 1f9d833867..b0c7a9da86 100644 --- a/public/language/es/admin/advanced/database.json +++ b/public/language/es/admin/advanced/database.json @@ -18,8 +18,8 @@ "mongo.resident-memory": "Memoria Residente", "mongo.virtual-memory": "Memoria Virtual", "mongo.mapped-memory": "Memoria Mapeada", - "mongo.bytes-in": "Bytes In", - "mongo.bytes-out": "Bytes Out", + "mongo.bytes-in": "Entradas de Bytes", + "mongo.bytes-out": "Salidas de Bytes", "mongo.num-requests": "Número de solicitudes", "mongo.raw-info": "Fila de Información MongoDB", "mongo.unauthorized": "NodeBB no pudo consultar las estadísticas relevantes en la base de datos MongoDB. Por favor verifique que el usuario usado por NodeBB contiene el roll "clusterMonitor" para la base de datos "admin".", @@ -47,6 +47,6 @@ "redis.raw-info": "Fila de Información de Redis", "postgres": "Postgres", - "postgres.version": "PostgreSQL Version", + "postgres.version": "Versión PostgreSQL", "postgres.raw-info": "Postgres Raw Info" } diff --git a/public/language/es/admin/appearance/customise.json b/public/language/es/admin/appearance/customise.json index c39c9bd604..f2eeeb071a 100644 --- a/public/language/es/admin/appearance/customise.json +++ b/public/language/es/admin/appearance/customise.json @@ -8,7 +8,7 @@ "custom-js.enable": "Activar Javascript personalizado", "custom-header": "Cabezera personalizada", - "custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <head> section of your forum's markup. Script tags are allowed, but are discouraged, as the Custom Javascript tab is available.", + "custom-header.description": "Ingrese HTML personalizado aquí (por ejemplo, Metaetiquetas, etc.), que se agregará al <head> sección del marcado de tu foro. Las etiquetas de script están permitidas, pero se desaconsejan, ya que Custom Javascript2 pestaña está disponible.", "custom-header.enable": "Activar cabecera personalizada", "custom-css.livereload": "Activar Recargar en Vivo", diff --git a/public/language/es/admin/development/info.json b/public/language/es/admin/development/info.json index 3b98b9c9b2..e245cd0b06 100644 --- a/public/language/es/admin/development/info.json +++ b/public/language/es/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - Tú estás en %1:%2", + "you-are-on": "Tú estas en %1:%2", + "ip": "IP %1", "nodes-responded": "¡%1 nodos respondieron en %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/es/admin/extend/plugins.json b/public/language/es/admin/extend/plugins.json index 2d04abbc6e..971086f54a 100644 --- a/public/language/es/admin/extend/plugins.json +++ b/public/language/es/admin/extend/plugins.json @@ -9,7 +9,7 @@ "plugin-search": "Buscar", "plugin-search-placeholder": "Búscando Plug-in", - "submit-anonymous-usage": "Submit anonymous plugin usage data.", + "submit-anonymous-usage": "Enviar datos anónimos de uso de complementos.", "reorder-plugins": "Re-ordenar Plug-ins", "order-active": "Ordenar Plug-ins Activos", "dev-interested": "¿Estas interesado en escribir plug-ins para NodeBB?", @@ -30,8 +30,8 @@ "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.", - "plugin-item.compatible": "This plugin works on NodeBB %1", - "plugin-item.not-compatible": "This plugin has no compatibility data, make sure it works before installing on your production environment.", + "plugin-item.compatible": "Este complemento funciona en NodeBB %1", + "plugin-item.not-compatible": "Este complemento no tiene datos de compatibilidad, asegúrese de que funcione antes de instalarlo en su entorno de producción.", "alert.enabled": "El plug-in esta Activo", "alert.disabled": "Plug-in Des-habilitado", diff --git a/public/language/es/admin/manage/tags.json b/public/language/es/admin/manage/tags.json index f8954aa9a2..b035acda85 100644 --- a/public/language/es/admin/manage/tags.json +++ b/public/language/es/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Haz clickaquí para visitar la página de configuración de etiquetas (tags).", "name": "Nombre de Etiqueta (tag)", - "alerts.editing-multiple": "Editando múltiples etiquetas (tags)", - "alerts.editing-x": "Editar \"%1\" etiqueta (tag)", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "¿Quieres borrar las etiquetas (tags) seleccionadas?", "alerts.update-success": "¡Etiqueta (tag) Actualizada!" } \ No newline at end of file diff --git a/public/language/es/admin/settings/advanced.json b/public/language/es/admin/settings/advanced.json index 1c24a7b8da..9b20f321de 100644 --- a/public/language/es/admin/settings/advanced.json +++ b/public/language/es/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Seguridad estricta del transporte", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/es/admin/settings/general.json b/public/language/es/admin/settings/general.json index aa9d22e663..21c7b202a7 100644 --- a/public/language/es/admin/settings/general.json +++ b/public/language/es/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Ajustes del Sitio", "title": "Título del Sitio", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "La URL del título del sitio", "title.url-help": "Cuando se hace click en el título, enviar a los usuarios a esta dirección. Si se deja en blanco, el usuario será enviado al índice del foro.", @@ -31,5 +33,9 @@ "outgoing-links": "Enlaces a sitios externos", "outgoing-links.warning-page": "Usar Página de Advertencia para Enlaces a Sitios Externos", "search-default-sort-by": "Orden por defecto de la búsqueda", - "outgoing-links.whitelist": "Dominios permitidos que podrán evitar la página de advertencia" -} \ No newline at end of file + "outgoing-links.whitelist": "Dominios permitidos que podrán evitar la página de advertencia", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/es/admin/settings/group.json b/public/language/es/admin/settings/group.json index 60808df66e..cf0bc1be9e 100644 --- a/public/language/es/admin/settings/group.json +++ b/public/language/es/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Grupos Privados", "private-groups.help": "Si se habilita, unirse a grupos requiere la aprobación del dueño del grupo (Por defecto: habilitado)", "private-groups.warning": "¡Cuidado! Si esta opción está deshabilitada y tienes grupos privados, se convertirán en grupos públicos automáticamente.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Esta opción puede ser usadas para permitir a los usuarios seleccionar múltiples medallas de grupo, requiere de soporte del theme/plantilla.", "max-name-length": "Longitud Máxima de Nombre de Grupo", "max-title-length": "Longitud máxima del título del grupo", diff --git a/public/language/es/admin/settings/user.json b/public/language/es/admin/settings/user.json index 3574cf083c..d6f491fc78 100644 --- a/public/language/es/admin/settings/user.json +++ b/public/language/es/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Solo Email", "account-settings": "Configuración de la Cuenta", "gdpr_enabled": "Habilitar la recolección del consentimiento GDPR", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Desactivar cambios de nombre de usuario", "disable-email-changes": "Desactivar cambios de email", "disable-password-changes": "Desactivar cambios de contraseña", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Vigilando ", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignorando" -} \ No newline at end of file +} diff --git a/public/language/es/modules.json b/public/language/es/modules.json index 6fc4f88fea..0d64e45794 100644 --- a/public/language/es/modules.json +++ b/public/language/es/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Chats recientes", "chat.contacts": "Contactos", "chat.message-history": "Historial de mensajes", + "chat.message-deleted": "Message Deleted", "chat.options": "Opciones de chat", "chat.pop-out": "Mostrar en ventana independiente", "chat.minimize": "Minimizar", diff --git a/public/language/es/topic.json b/public/language/es/topic.json index 5d2515de32..e75e8e4d8e 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -18,13 +18,13 @@ "last_reply_time": "Última respuesta", "reply-as-topic": "Responder como tema", "guest-login-reply": "Accede para responder", - "login-to-view": "🔒 Log in to view", + "login-to-view": "🔒 Inicie sesión para ver", "edit": "Editar", "delete": "Borrar", "purge": "Purgar", "restore": "Restaurar", "move": "Mover", - "change-owner": "Change Owner", + "change-owner": "Cambiar propietario", "fork": "Dividir", "link": "Link", "share": "Compartir", @@ -43,19 +43,19 @@ "not_following_topic.message": "Podras ver este tema en la lista de no leidos, pero no recibirás notificaciones cuando alguien escriba en él.", "ignoring_topic.message": "Ya no verás este tema en no leídos. Serás notificado si te mencionan o te votan.", "login_to_subscribe": "Por favor, conéctate para subscribirte a este tema.", - "markAsUnreadForAll.success": "Publicación marcada como no leída para todos.", + "markAsUnreadForAll.success": "Tema marcado como no leído para todos.", "mark_unread": "Marcar no leído", "mark_unread.success": "Tema marcado como no leído.", "watch": "Seguir", "unwatch": "Dejar de seguir", "watch.title": "Serás notificado cuando haya nuevas respuestas en este tema", "unwatch.title": "Dejar de seguir este tema", - "share_this_post": "Compartir este post", + "share_this_post": "Compartir este mensaje", "watching": "Siguiendo", "not-watching": "No siguiendo", "ignoring": "Ignorando", "watching.description": "Notificarme de nuevas respuestas.
Mostrar tema en no leídos. ", - "not-watching.description": "No notificarme de nuevas respuestas.
Mostrar tema en no leídos si sigo esa categoría. ", + "not-watching.description": "No notificarme de nuevas respuestas.
No mostrar tema en no leídos. ", "ignoring.description": "No notificarme de nuevas respuestas.
No mostrar tema en no leídos. ", "thread_tools.title": "Herramientas", "thread_tools.markAsUnreadForAll": "Marcar todo como no leído", @@ -64,13 +64,13 @@ "thread_tools.lock": "Cerrar tema", "thread_tools.unlock": "Reabrir tema", "thread_tools.move": "Mover tema", - "thread_tools.move-posts": "Mover Posts", + "thread_tools.move-posts": "Mover mensajes", "thread_tools.move_all": "Mover todo", - "thread_tools.change_owner": "Change Owner", + "thread_tools.change_owner": "Cambiar propietario", "thread_tools.select_category": "Seleccionar categoría", "thread_tools.fork": "Dividir tema", "thread_tools.delete": "Borrar tema", - "thread_tools.delete-posts": "Eliminar publicaciones", + "thread_tools.delete-posts": "Eliminar mensajes", "thread_tools.delete_confirm": "¿Estás seguro que deseas eliminar este tema?", "thread_tools.restore": "Restaurar tema", "thread_tools.restore_confirm": "¿Estás seguro que deseas restaurar este tema?", @@ -79,8 +79,8 @@ "thread_tools.merge_topics": "Fusionar temas", "thread_tools.merge": "Fusionar", "topic_move_success": "El tema ha sido movido correctamente a %1", - "post_delete_confirm": "¿Estás seguro de que quieres eliminar esta respuesta?", - "post_restore_confirm": "¿Estás seguro de que quieres restaurar esta respuesta?", + "post_delete_confirm": "¿Estás seguro de que quieres eliminar este mensaje?", + "post_restore_confirm": "¿Estás seguro de que quieres restaurar este mensaje?", "post_purge_confirm": "¡Estás seguro de que quieres purgar esta publicación?", "load_categories": "Cargando categorías", "confirm_move": "Mover", @@ -88,11 +88,11 @@ "bookmark": "Marcador", "bookmarks": "Marcadores", "bookmarks.has_no_bookmarks": "No tienes ningún marcador aun.", - "loading_more_posts": "Cargando más publicaciones", + "loading_more_posts": "Cargando más mensajes", "move_topic": "Mover tema", "move_topics": "Mover temas", "move_post": "Mover mensaje", - "post_moved": "¡Publicación movida correctamente!", + "post_moved": "¡Mensaje movido!", "fork_topic": "Dividir tema", "fork_topic_instruction": "Pulsa en los mensajes que quieres dividir", "fork_no_pids": "¡No has seleccionado ningún mensaje!", @@ -101,9 +101,9 @@ "delete_posts_instruction": "Haz click en los mensajes que quieres eliminar/limpiar", "merge_topics_instruction": "Selecciona los temas que quieres fusionar", "move_posts_instruction": "Pulsa en los posts que quieras mover", - "change_owner_instruction": "Click the posts you want to assign to another user", + "change_owner_instruction": "Haz click en los mensajes que quieres asignar a otro usuario", "composer.title_placeholder": "Ingresa el título de tu tema...", - "composer.handle_placeholder": "Nombre", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Descartar", "composer.submit": "Enviar", "composer.replying_to": "En respuesta a %1", @@ -129,11 +129,11 @@ "stale.create": "Crear un nuevo hilo", "stale.reply_anyway": "Publicar este hilo de todos modos.", "link_back": "Re: [%1](%2)", - "diffs.title": "Historial de Edición del Post", + "diffs.title": "Historial de Ediciones", "diffs.description": "Este post ha tenido %1 revisión(es). Pulsa una de ellas para ver el contenido del post en ese momento.", "diffs.no-revisions-description": "Este post ha tenido %1 revisión(es).", "diffs.current-revision": "revisión actual", "diffs.original-revision": "revisión original", - "timeago_later": "%1 later", - "timeago_earlier": "%1 earlier" + "timeago_later": "%1 después", + "timeago_earlier": "%1 antes" } \ No newline at end of file diff --git a/public/language/et/admin/development/info.json b/public/language/et/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/et/admin/development/info.json +++ b/public/language/et/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/et/admin/manage/tags.json b/public/language/et/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/et/admin/manage/tags.json +++ b/public/language/et/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/et/admin/settings/advanced.json b/public/language/et/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/et/admin/settings/advanced.json +++ b/public/language/et/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/et/admin/settings/general.json b/public/language/et/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/et/admin/settings/general.json +++ b/public/language/et/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/et/admin/settings/group.json b/public/language/et/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/et/admin/settings/group.json +++ b/public/language/et/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/et/admin/settings/user.json b/public/language/et/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/et/admin/settings/user.json +++ b/public/language/et/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/et/modules.json b/public/language/et/modules.json index 4eedbed067..24a78eb61d 100644 --- a/public/language/et/modules.json +++ b/public/language/et/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Hiljutised vestlused", "chat.contacts": "Kontaktid", "chat.message-history": "Sõnumite ajalugu", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop-out vestlus", "chat.minimize": "Minimize", diff --git a/public/language/et/topic.json b/public/language/et/topic.json index 1d09b075ca..c8acd6777d 100644 --- a/public/language/et/topic.json +++ b/public/language/et/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Sisesta teema pealkiri siia...", - "composer.handle_placeholder": "Nimi", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Katkesta", "composer.submit": "Postita", "composer.replying_to": "Vastad %1'le", diff --git a/public/language/fa-IR/admin/development/info.json b/public/language/fa-IR/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/fa-IR/admin/development/info.json +++ b/public/language/fa-IR/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/fa-IR/admin/manage/tags.json b/public/language/fa-IR/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/fa-IR/admin/manage/tags.json +++ b/public/language/fa-IR/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/fa-IR/admin/settings/advanced.json b/public/language/fa-IR/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/fa-IR/admin/settings/advanced.json +++ b/public/language/fa-IR/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/fa-IR/admin/settings/general.json b/public/language/fa-IR/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/fa-IR/admin/settings/general.json +++ b/public/language/fa-IR/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/fa-IR/admin/settings/group.json b/public/language/fa-IR/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/fa-IR/admin/settings/group.json +++ b/public/language/fa-IR/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/fa-IR/admin/settings/user.json b/public/language/fa-IR/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/fa-IR/admin/settings/user.json +++ b/public/language/fa-IR/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/fa-IR/modules.json b/public/language/fa-IR/modules.json index 682f1b223a..820fb09912 100644 --- a/public/language/fa-IR/modules.json +++ b/public/language/fa-IR/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "گفتگو های اخیر", "chat.contacts": "تماس‌ها", "chat.message-history": "تاریخچه پیام‌ها", + "chat.message-deleted": "Message Deleted", "chat.options": "تنظیمات چت", "chat.pop-out": "پاپ آپ گفتگو", "chat.minimize": "کوچک کردن", diff --git a/public/language/fa-IR/topic.json b/public/language/fa-IR/topic.json index 3fd61dd5e3..453d9ccae2 100644 --- a/public/language/fa-IR/topic.json +++ b/public/language/fa-IR/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "بر روی پستی که می خواهید انتقال دهید کلیک کنید", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "عنوان موضوعتان را اینجا بنویسید...", - "composer.handle_placeholder": "نام", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "دور بیانداز", "composer.submit": "بفرست", "composer.replying_to": "پاسخ به %1", diff --git a/public/language/fi/admin/development/info.json b/public/language/fi/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/fi/admin/development/info.json +++ b/public/language/fi/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/fi/admin/manage/tags.json b/public/language/fi/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/fi/admin/manage/tags.json +++ b/public/language/fi/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/fi/admin/settings/advanced.json b/public/language/fi/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/fi/admin/settings/advanced.json +++ b/public/language/fi/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/fi/admin/settings/general.json b/public/language/fi/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/fi/admin/settings/general.json +++ b/public/language/fi/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/fi/admin/settings/group.json b/public/language/fi/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/fi/admin/settings/group.json +++ b/public/language/fi/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/fi/admin/settings/user.json b/public/language/fi/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/fi/admin/settings/user.json +++ b/public/language/fi/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json index b588812f96..3742a6379c 100644 --- a/public/language/fi/modules.json +++ b/public/language/fi/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Viimeisimmät keskustelut", "chat.contacts": "Contacts", "chat.message-history": "Viestihistoria", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out chat", "chat.minimize": "Minimize", diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json index 5f3ec66000..2c3a89d3cc 100644 --- a/public/language/fi/topic.json +++ b/public/language/fi/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Valitse viestit jotka haluat siirtää", "change_owner_instruction": "Valitse viestit jotka haluat siirtää toiselle henkilölle", "composer.title_placeholder": "Syötä aiheesi otsikko tähän...", - "composer.handle_placeholder": "Nimi", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Hylkää", "composer.submit": "Lähetä", "composer.replying_to": "Vastaus viestiin %1", diff --git a/public/language/fr/admin/development/info.json b/public/language/fr/admin/development/info.json index 26f71e4e97..206bd56c78 100644 --- a/public/language/fr/admin/development/info.json +++ b/public/language/fr/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - Vous êtes sur %1:%2", + "you-are-on": "Vous êtes sur %1:%2", + "ip": "IP %1", "nodes-responded": "%1 noeuds ont répondu en %2ms !", "host": "hôte", "pid": "pid", diff --git a/public/language/fr/admin/manage/tags.json b/public/language/fr/admin/manage/tags.json index c7379de29a..437239e31d 100644 --- a/public/language/fr/admin/manage/tags.json +++ b/public/language/fr/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Cliquez ici pour visiter la page de paramètres des mots clés.", "name": "Nom du mot-clés", - "alerts.editing-multiple": "Éditer plusieurs mots-clés", - "alerts.editing-x": "Éditer le mot-clés %1", + "alerts.editing": "Modification des tag(s)", "alerts.confirm-delete": "Vous-voulez réellement supprimer les mots-clés sélectionnés ?", "alerts.update-success": "Mot-clés mis à jour !" } \ No newline at end of file diff --git a/public/language/fr/admin/manage/users.json b/public/language/fr/admin/manage/users.json index 5e86e4f4da..a4f0c3152b 100644 --- a/public/language/fr/admin/manage/users.json +++ b/public/language/fr/admin/manage/users.json @@ -7,13 +7,13 @@ "send-validation-email": "Envoyer un e-mail de vérification", "password-reset-email": "Envoyer un e-mail de réinitialisation du mot de passe", "force-password-reset": "Forcer la réinitialisation du mot de passe et déconnecter l'utilisateur", - "ban": "Bannir les utilisateurs", - "temp-ban": "Bannir temporairement les utilisateurs", - "unban": "Dé-bannir les utilisateurs", + "ban": "Utilisateur(s) banni(s)", + "temp-ban": "Utilisateur(s) temporairement banni(s)", + "unban": "Dé-bannir le(s) utilisateur(s)", "reset-lockout": "Supprimer le blocage", "reset-flags": "Supprimer les signalements", - "delete": "Supprimer les utilisateurs", - "purge": "Supprimer les utilisateurs ainsi que leurs contenus", + "delete": "Supprimer le(s) utilisateur(s)", + "purge": "Supprimer le(s) utilisateur(s) ainsi que leurs contenus", "download-csv": "Télécharger au format CSV", "manage-groups": "Gérer les groupes", "add-group": "Ajouter un groupe", diff --git a/public/language/fr/admin/settings/advanced.json b/public/language/fr/admin/settings/advanced.json index ea7c6f1568..624b245edc 100644 --- a/public/language/fr/admin/settings/advanced.json +++ b/public/language/fr/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "\nAccess-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Activer HSTS (recommandé)", + "hsts.maxAge": "HSTS Age Max", "hsts.subdomains": "Inclure les sous-domaines dans l'en-tête HSTS", "hsts.preload": "Autoriser le préchargement de l'en-tête HSTS", "hsts.help": "Si activé, un en-tête HSTS sera défini pour ce site. Vous pouvez choisir d'inclure des sous-domaines et des indicateurs de préchargement dans votre en-tête. En cas de doute, ne cochez pas l'option. Plus d'informations", diff --git a/public/language/fr/admin/settings/general.json b/public/language/fr/admin/settings/general.json index 5dd47e1379..19e2cb32ad 100644 --- a/public/language/fr/admin/settings/general.json +++ b/public/language/fr/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Réglages du site", "title": "Titre du site", + "title.short": "Titre court", + "title.short-placeholder": "Si aucun titre court n'est spécifié, le titre du site sera utilisé", "title.url": "URL", "title.url-placeholder": "URL du titre du site", "title.url-help": "Adresse à laquelle l'utilisateur est renvoyé lors du clic sur le titre. Si ce champ est vide, l'adresse est celle de l'index du forum.", @@ -31,5 +33,9 @@ "outgoing-links": "Liens sortants", "outgoing-links.warning-page": "Utiliser la page d'avertissement pour liens sortants", "search-default-sort-by": "Tri par défaut de la recherche", - "outgoing-links.whitelist": "Domaines à inclure dans la liste blanche pour passer la page d'avertissement." -} \ No newline at end of file + "outgoing-links.whitelist": "Domaines à inclure dans la liste blanche pour passer la page d'avertissement.", + "site-colors": "Métadonnées des couleurs du site", + "theme-color": "Couleur du thème", + "background-color": "Couleur de l'arrière plan", + "background-color-help": "Couleur utilisée pour l'arrière-plan de l'écran de démarrage lorsque le site Web est installé en tant que PWA" +} diff --git a/public/language/fr/admin/settings/group.json b/public/language/fr/admin/settings/group.json index 75c107c3fb..c32ecfb66f 100644 --- a/public/language/fr/admin/settings/group.json +++ b/public/language/fr/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Groupes privés", "private-groups.help": "Si cette case est cochée, rejoindre un groupe nécessitera l'accord d'un propriétaire du groupe (Par défaut : activé)", "private-groups.warning": "Attention ! Si cette option est désactivée et que vous avez des groupes privés, ils deviendront automatiquement publics.", + "allow-multiple-badges": "Autoriser de multiple badges", "allow-multiple-badges-help": "Cet affichage peut être utilisé pour permettre aux utilisateurs de sélectionner plusieurs badges de groupe, nécessite que votre thème le supporte.", "max-name-length": "Longueur maximum des noms de groupes", "max-title-length": "Longueur maximale du titre de groupe", diff --git a/public/language/fr/admin/settings/user.json b/public/language/fr/admin/settings/user.json index 2fac6974f0..763a81cbda 100644 --- a/public/language/fr/admin/settings/user.json +++ b/public/language/fr/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "E-mail uniquement", "account-settings": "Paramètres du compte", "gdpr_enabled": "Activer le consentement GRPD", - "gdpr_enabled_help": "Lorsque cette option est activée, tous les nouveaux utilisateurs devront explicitement donner leur consentement pour la collecte et l'utilisation de données en vertu du règlement général sur la protection des données (GRPD). Remarque: l'activation du GRPD ne force pas les utilisateurs préexistants à donner leur consentement. Pour ce faire, vous devrez installer le plugin GRPD.", + "gdpr_enabled_help": "Une fois activé, tous les nouveaux inscrits seront tenus de donner explicitement leur consentement pour la collecte et l'utilisation des données en vertu du Règlement Général sur la Protection des Données (RGPD). Remarque: l'activation du RGPD n'oblige pas les utilisateurs préexistants à donner leur consentement. Pour ce faire, vous devrez installer le plugin GDPR.", "disable-username-changes": "Désactiver le changement de nom d'utilisateur", "disable-email-changes": "Désactiver le changement d'adresse e-mail", "disable-password-changes": "Désactiver le changement de mot de passe", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Abonné", "categoryWatchState.notwatching": "Non abonné", "categoryWatchState.ignoring": "Ignoré" -} \ No newline at end of file +} diff --git a/public/language/fr/email.json b/public/language/fr/email.json index 50649893d2..2c9a3cc67b 100644 --- a/public/language/fr/email.json +++ b/public/language/fr/email.json @@ -1,6 +1,6 @@ { "test-email.subject": "Email Test", - "password-reset-requested": "Demande de réinitialisation de mot de passe!", + "password-reset-requested": "Demande de réinitialisation de mot de passe !", "welcome-to": "Bienvenue sur %1", "invite": "Invitation de %1", "greeting_no_name": "Bonjour", @@ -12,7 +12,7 @@ "welcome.text3": "Un administrateur a accepté votre demande d'inscription. Vous pouvez maintenant vous connecter avec vos identifiants/mots de passe.", "welcome.cta": "Cliquez ici pour confirmer votre adresse e-mail", "invitation.text1": "%1 vous a invité à rejoindre %2", - "invitation.text2": "Votre invitation va expirée dans %1 jours.", + "invitation.text2": "Votre invitation va expirer dans %1 jours.", "invitation.cta": "Cliquez ici pour créer votre compte.", "reset.text1": "Nous avons reçu une demande de réinitialisation de votre mot de passe, probablement parce que vous l'avez oublié. Si ce n'est pas le cas, veuillez ignorer cet email.", "reset.text2": "Pour confirmer la réinitialisation de votre mot de passe, veuillez cliquer sur le lien suivant :", diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json index 74583d221d..e59efef046 100644 --- a/public/language/fr/modules.json +++ b/public/language/fr/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Discussions récentes", "chat.contacts": "Contacts", "chat.message-history": "Historique des messages", + "chat.message-deleted": "Message supprimé", "chat.options": "Options", "chat.pop-out": "Afficher la discussion", "chat.minimize": "Réduire", diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json index 94c160dbfc..ea8c6df18a 100644 --- a/public/language/fr/topic.json +++ b/public/language/fr/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Cliquez sur les messages que vous souhaitez déplacer", "change_owner_instruction": "Cliquez sur les messages que vous souhaitez attribuer à un autre utilisateur.", "composer.title_placeholder": "Entrer le titre du sujet ici…", - "composer.handle_placeholder": "Nom", + "composer.handle_placeholder": "Entrez votre nom/identifiant ici", "composer.discard": "Abandonner", "composer.submit": "Envoyer", "composer.replying_to": "En réponse à %1", diff --git a/public/language/gl/admin/development/info.json b/public/language/gl/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/gl/admin/development/info.json +++ b/public/language/gl/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/gl/admin/manage/tags.json b/public/language/gl/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/gl/admin/manage/tags.json +++ b/public/language/gl/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/gl/admin/settings/advanced.json b/public/language/gl/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/gl/admin/settings/advanced.json +++ b/public/language/gl/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/gl/admin/settings/general.json b/public/language/gl/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/gl/admin/settings/general.json +++ b/public/language/gl/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/gl/admin/settings/group.json b/public/language/gl/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/gl/admin/settings/group.json +++ b/public/language/gl/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/gl/admin/settings/user.json b/public/language/gl/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/gl/admin/settings/user.json +++ b/public/language/gl/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json index 82fe9e56a0..858b05336e 100644 --- a/public/language/gl/modules.json +++ b/public/language/gl/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Charlas Recentes", "chat.contacts": "Contactos", "chat.message-history": "Historial de mensaxes", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Marchar do chat", "chat.minimize": "Minimize", diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json index f9414478a3..a4514f2daf 100644 --- a/public/language/gl/topic.json +++ b/public/language/gl/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Introduce o título do teu tema", - "composer.handle_placeholder": "Nome", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Descartar", "composer.submit": "Enviar", "composer.replying_to": "En resposta a %1", diff --git a/public/language/he/admin/development/info.json b/public/language/he/admin/development/info.json index c4ee8fc524..5ce2a576b4 100644 --- a/public/language/he/admin/development/info.json +++ b/public/language/he/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/he/admin/manage/registration.json b/public/language/he/admin/manage/registration.json index f51b4d56e6..189338e6bb 100644 --- a/public/language/he/admin/manage/registration.json +++ b/public/language/he/admin/manage/registration.json @@ -1,20 +1,20 @@ { - "queue": "Queue", - "description": "There are no users in the registration queue.
To enable this feature, go to Settings → User → User Registration and set Registration Type to \"Admin Approval\".", + "queue": "תור", + "description": "אין משתמשים בתור ההרשמה.
כדי לאפשר את תור ההרשמה, גשו להגדרות → משתמש → רישום משתמש והגדר את סוג רישום ל\"אישור מנהל\".", - "list.name": "Name", - "list.email": "Email", + "list.name": "שם", + "list.email": "אימייל", "list.ip": "IP", - "list.time": "Time", + "list.time": "זמן", "list.username-spam": "Frequency: %1 Appears: %2 Confidence: %3", "list.email-spam": "Frequency: %1 Appears: %2", "list.ip-spam": "Frequency: %1 Appears: %2", - "invitations": "Invitations", + "invitations": "הזמנות", "invitations.description": "Below is a complete list of invitations sent. Use ctrl-f to search through the list by email or username.

The username will be displayed to the right of the emails for users who have redeemed their invitations.", - "invitations.inviter-username": "Inviter Username", - "invitations.invitee-email": "Invitee Email", - "invitations.invitee-username": "Invitee Username (if registered)", + "invitations.inviter-username": "משתמש מזמין", + "invitations.invitee-email": "מייל מוזמן", + "invitations.invitee-username": "משתמש מוזמן (אם נרשם)", - "invitations.confirm-delete": "Are you sure you wish to delete this invitation?" + "invitations.confirm-delete": "האם אתה בטוח שאתה רוצה למחוק את ההזמנה?" } \ No newline at end of file diff --git a/public/language/he/admin/manage/tags.json b/public/language/he/admin/manage/tags.json index e6ca573ad1..1a5a8a49b1 100644 --- a/public/language/he/admin/manage/tags.json +++ b/public/language/he/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "שם תג", - "alerts.editing-multiple": "עריכת מספר תגים", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "האם תרצה למחוק את התגים שנבחרו?", "alerts.update-success": "תג עודכן!" } \ No newline at end of file diff --git a/public/language/he/admin/settings/advanced.json b/public/language/he/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/he/admin/settings/advanced.json +++ b/public/language/he/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/he/admin/settings/general.json b/public/language/he/admin/settings/general.json index 42ea725ff3..11a34c8beb 100644 --- a/public/language/he/admin/settings/general.json +++ b/public/language/he/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "הגדרות האתר", "title": "כותרת האתר", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "כתובת האתר", "title.url-placeholder": "כתובת אתר זה", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/he/admin/settings/group.json b/public/language/he/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/he/admin/settings/group.json +++ b/public/language/he/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json index 9f029bf777..0bda70ec12 100644 --- a/public/language/he/admin/settings/user.json +++ b/public/language/he/admin/settings/user.json @@ -1,23 +1,23 @@ { - "authentication": "Authentication", - "require-email-confirmation": "Require Email Confirmation", - "email-confirm-interval": "User may not resend a confirmation email until", - "email-confirm-email2": "minutes have elapsed", - "allow-login-with": "Allow login with", - "allow-login-with.username-email": "Username or Email", - "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", - "account-settings": "Account Settings", + "authentication": "אימות", + "require-email-confirmation": "דורש אישור על ידי כתובת מייל", + "email-confirm-interval": "המשתמש לא יכול לשלוח הודעת אישור מייל עד", + "email-confirm-email2": "דקות חלפו", + "allow-login-with": "אפשר התחברות עם", + "allow-login-with.username-email": "שם משתמש או סיסמא", + "allow-login-with.username": "שם משתמש בלבד", + "allow-login-with.email": "כתובת מייל בלבד", + "account-settings": "הגדרות חשבון", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", - "disable-username-changes": "Disable username changes", - "disable-email-changes": "Disable email changes", - "disable-password-changes": "Disable password changes", - "allow-account-deletion": "Allow account deletion", - "hide-fullname": "Hide fullname from users", - "hide-email": "Hide email from users", - "themes": "Themes", - "disable-user-skins": "Prevent users from choosing a custom skin", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "disable-username-changes": "בטל שינויי שם משתמש", + "disable-email-changes": "בטל שינויי כתובת מייל", + "disable-password-changes": "בטל שינויי סיסמא", + "allow-account-deletion": "אפשר מחיקת חשבונות", + "hide-fullname": "החבא שם מלא ממשתמשים", + "hide-email": "החבא כתובת מייל ממשתמשים", + "themes": "ערכות נושא", + "disable-user-skins": "אל תאפשר למשתמשים לבחור ערכת נושא", "account-protection": "Account Protection", "admin-relogin-duration": "Admin relogin duration (minutes)", "admin-relogin-duration-help": "After a set amount of time accessing the admin section will require re-login, set to 0 to disable", @@ -33,21 +33,21 @@ "online-cutoff": "Minutes after user is considered inactive", "online-cutoff-help": "If user performs no actions for this duration, they are considered inactive and they do not receive realtime updates.", "registration": "User Registration", - "registration-type": "Registration Type", - "registration-approval-type": "Registration Approval Type", - "registration-type.normal": "Normal", - "registration-type.admin-approval": "Admin Approval", - "registration-type.admin-approval-ip": "Admin Approval for IPs", - "registration-type.invite-only": "Invite Only", - "registration-type.admin-invite-only": "Admin Invite Only", - "registration-type.disabled": "No registration", - "registration-type.help": "Normal - Users can register from the /register page.
\nInvite Only - Users can invite others from the users page.
\nAdmin Invite Only - Only administrators can invite others from users and admin/manage/users pages.
\nNo registration - No user registration.
", - "registration-approval-type.help": "Normal - Users are registered immediately.
\nAdmin Approval - User registrations are placed in an approval queue for administrators.
\nAdmin Approval for IPs - Normal for new users, Admin Approval for IP addresses that already have an account.
", - "registration.max-invites": "Maximum Invitations per User", - "max-invites": "Maximum Invitations per User", - "max-invites-help": "0 for no restriction. Admins get infinite invitations
Only applicable for \"Invite Only\"", - "invite-expiration": "Invite expiration", - "invite-expiration-help": "# of days invitations expire in.", + "registration-type": "סוג השרמה", + "registration-approval-type": "סוג אישור הרשמה", + "registration-type.normal": "רגיל", + "registration-type.admin-approval": "אישור מנהל", + "registration-type.admin-approval-ip": "אישור מנהל לכתובות IP", + "registration-type.invite-only": "הזמנה בלבד", + "registration-type.admin-invite-only": "הזמנת מנהל בלבד", + "registration-type.disabled": "בטל הרשמה", + "registration-type.help": "רגיל - משתמשים יכולים להירשם על ידי שימוש בדף /register.
\nהזמנה בלבד - משתמשים אחרים יכולים להזמין משתמשים מדף המשתמש.
\nהזמנת מנהל בלבד - רק מנהלים יכולים להזמין משתמשים אחרים מדף
המשתמש ודף ההנהלת משתמשים.
\nבטל הרשמה - לא ניתן להירשם.
ד", + "registration-approval-type.help": "רגיל - משתמשים נרשמים באופן מיידי.
\nאישור מנהל - משתמשים אשר נרשמו מוכנים לתוך רשימת אישור למנהלים.
\nאישור מנהל לכתובות IP - רגיל למשתמשים חדשים, אישור מנהל לכתובות IP אשר כבר מקושר אליהם חשבון.
", + "registration.max-invites": "מרב ההזמנות למשתמש", + "max-invites": "מרב ההזמנות למשתמש", + "max-invites-help": "0 בשביל לבטל הגבלה. מנהלים מקבלים אינסוף הזמנות
תקף רק ל-\"הזמנה בלבד\"", + "invite-expiration": "תוקף ההזמנה", + "invite-expiration-help": "הזמנות יפוגו ב# ימים.", "min-username-length": "Minimum Username Length", "max-username-length": "Maximum Username Length", "min-password-length": "Minimum Password Length", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/he/modules.json b/public/language/he/modules.json index d540b46276..c6ca721a03 100644 --- a/public/language/he/modules.json +++ b/public/language/he/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "צ'אטים אחרונים", "chat.contacts": "אנשי קשר", "chat.message-history": "היסטוריית הודעות", + "chat.message-deleted": "Message Deleted", "chat.options": "אפשרויות לשיחה", "chat.pop-out": "הוצא את חלון הצ'אט", "chat.minimize": "צמצם", diff --git a/public/language/he/topic.json b/public/language/he/topic.json index c8d9acbe8d..ad5c929045 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "לחץ על הפוסטים שאתה רוצה להזיז", "change_owner_instruction": "לחץ על ההודעה שהנך רוצה לשנות את בעל ההודעה", "composer.title_placeholder": "הכנס את כותרת הנושא כאן...", - "composer.handle_placeholder": "שם", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "ביטול", "composer.submit": "שלח", "composer.replying_to": "מגיב ל %1", diff --git a/public/language/he/user.json b/public/language/he/user.json index 52877f1892..ca31c2f78e 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -29,7 +29,7 @@ "change_all": "שנה הכל", "watched": "נצפה", "ignored": "התעלם", - "default-category-watch-state": "Default category watch state", + "default-category-watch-state": "מצב מעקב על קטגוריה בברירת מחדל", "followers": "עוקבים", "following": "עוקב אחרי", "blocks": "חסימות", @@ -96,7 +96,7 @@ "has_no_upvoted_posts": "המשתמש טרם הצביע בעד פוסטים כלשהם.", "has_no_downvoted_posts": "המשתמש טרם הצביע נגד פוסטים כלשהם.", "has_no_voted_posts": "למשתמש אין פוסטים שהוצבעו", - "has_no_blocks": "You have blocked no users.", + "has_no_blocks": "לא חסמת אף משתמש.", "email_hidden": "כתובת אימייל מוסתרת", "hidden": "מוסתר", "paginate_description": "הצג נושאים ופוסטים כדפים במקום כרשימת גלילה אין-סופית", @@ -114,7 +114,7 @@ "upvote-notif-freq.all": "כל ההצבעות החיוביות", "upvote-notif-freq.first": "First Per Post", "upvote-notif-freq.everyTen": "כל 10 הצבעות חיוביות", - "upvote-notif-freq.threshold": "On 1, 5, 10, 25, 50, 100, 150, 200...", + "upvote-notif-freq.threshold": "ב-1, 5, 10, 25, 50, 100, 150, 200...", "upvote-notif-freq.logarithmic": "ב10, 100, 1000...", "upvote-notif-freq.disabled": "מבוטל", "browsing": "הגדרות צפייה", @@ -125,7 +125,7 @@ "follow_topics_you_reply_to": "עקוב אחר נושאים שהגבת עליהם", "follow_topics_you_create": "עקוב אחר נושאים שפרסמת", "grouptitle": "כותרת הקבוצה", - "group-order-help": "Select a group and use the arrows to order titles", + "group-order-help": "בחר קבוצה והשתמש בחצים על מנת לארגן כותרות", "no-group-title": "ללא כותרת לקבוצה", "select-skin": "בחר מראה", "select-homepage": "בחר דף בית", @@ -152,7 +152,7 @@ "info.moderation-note": "הערת מודרטור", "info.moderation-note.success": "הערת מודרטור נשמרה", "info.moderation-note.add": "הוסף הערה", - "sessions.description": "This page allows you to view any active sessions on this forum and revoke them if necessary. You can revoke your own session by logging out of your account.", + "sessions.description": "דף זה מאפשר לך לראות את כל הסשנים הפעילים בפורום זה ולבטל אותם במידת הצורך. אתה יכול לבטל את הסשן שלך על ידי התנתקות מהמשתמש.", "consent.title": "תנאי השימוש באתר", "consent.lead": "אתר זה אוסף ומעבד נתונים הכוללים בחלקם את המידע האישי שלך.", "consent.intro": "אנו משתמשים במידע שנאסף כדי להתאים אישית את החוויה שלך, וכן לקשר את ההודעות שאתה מבצע לחשבון המשתמש שלך. במהלך שלב ההרשמה התבקשת לספק שם משתמש וכתובת דוא\"ל, תוכל גם לספק מידע נוסף כדי להשלים את פרופיל המשתמש שלך באתר זה.

אנו שומרים ומעבדים מידע זה. אתה יכול לבטל את הסכמתך בכל עת על ידי מחיקת החשבון שלך. בכל עת תוכל לבקש עותק של חשבונך לאתר זה, באמצעות דף זה.

אם יש לך שאלות או חששות, אנו ממליצים לך ליצור קשר עם צוות הניהול של האתר.", diff --git a/public/language/hr/admin/development/info.json b/public/language/hr/admin/development/info.json index aa9411218f..14cb68bb9d 100644 --- a/public/language/hr/admin/development/info.json +++ b/public/language/hr/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - Vi ste %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "Domaćin", "pid": "pid", diff --git a/public/language/hr/admin/manage/tags.json b/public/language/hr/admin/manage/tags.json index ade0f1ac05..9a63c79b34 100644 --- a/public/language/hr/admin/manage/tags.json +++ b/public/language/hr/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Kliknite ovdje za posjetu postavki oznaka", "name": "Ime oznake", - "alerts.editing-multiple": "Uređivanje više oznaka", - "alerts.editing-x": "Uredi \"%1\" oznake", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Želite li obrisati odabrane oznake?", "alerts.update-success": "Oznake promijenjene!" } \ No newline at end of file diff --git a/public/language/hr/admin/settings/advanced.json b/public/language/hr/admin/settings/advanced.json index 6d58455f7f..5d58517e96 100644 --- a/public/language/hr/admin/settings/advanced.json +++ b/public/language/hr/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/hr/admin/settings/general.json b/public/language/hr/admin/settings/general.json index dfcb6332fe..72b76bfe05 100644 --- a/public/language/hr/admin/settings/general.json +++ b/public/language/hr/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Postavke stranice", "title": "Naslov stranice", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "outgoing-links": "Odlazne poveznice", "outgoing-links.warning-page": "Koristi upozorenje za odlazne poveznice", "search-default-sort-by": "Pretraži zadani poredak", - "outgoing-links.whitelist": "Domene za koje se ne koristi odlazno upozorenje" -} \ No newline at end of file + "outgoing-links.whitelist": "Domene za koje se ne koristi odlazno upozorenje", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/hr/admin/settings/group.json b/public/language/hr/admin/settings/group.json index 38d9c7b362..7d82af1610 100644 --- a/public/language/hr/admin/settings/group.json +++ b/public/language/hr/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Privatne grupe", "private-groups.help": "Ako je uključeno,ulazak u grupe zahtjevati će odobrenje vlasnika grupe (Default: enabled)", "private-groups.warning": "Pazi! Ako je ova opcija isključena,a imate privatne grupe,automatski će postati javne.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maksimalna dužina imena grupe", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/hr/admin/settings/user.json b/public/language/hr/admin/settings/user.json index 0c81511c31..def91bd53a 100644 --- a/public/language/hr/admin/settings/user.json +++ b/public/language/hr/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Samo email", "account-settings": "Postavke računa", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "onemogući promjenu korisničkog imena", "disable-email-changes": "Onemogući promjenu emaila", "disable-password-changes": "Onemogući promjenu lozinke", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/hr/modules.json b/public/language/hr/modules.json index 8cb0647075..a3da78cab2 100644 --- a/public/language/hr/modules.json +++ b/public/language/hr/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Nedavni razgovori", "chat.contacts": "Kontakti", "chat.message-history": "Povijest razgovora", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out razgovor", "chat.minimize": "Smanji", diff --git a/public/language/hr/topic.json b/public/language/hr/topic.json index 6f2593f8a7..c4c71c1835 100644 --- a/public/language/hr/topic.json +++ b/public/language/hr/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Unesite naslov teme ovdje ...", - "composer.handle_placeholder": "Ime", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Odbaci", "composer.submit": "Podnesi", "composer.replying_to": "Odgovori na %1", diff --git a/public/language/hu/admin/development/info.json b/public/language/hu/admin/development/info.json index 8591a2291e..abf2d247a4 100644 --- a/public/language/hu/admin/development/info.json +++ b/public/language/hu/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Infó - Most épp itt vagy: %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 csomópont válaszolt %2mp-n belül!", "host": "host", "pid": "pid", diff --git a/public/language/hu/admin/manage/tags.json b/public/language/hu/admin/manage/tags.json index 51fd849c72..a2157638ac 100644 --- a/public/language/hu/admin/manage/tags.json +++ b/public/language/hu/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Kattints ide a címke beállítások lap meglátogatásához.", "name": "Címkenév", - "alerts.editing-multiple": "Több címke szerkesztése", - "alerts.editing-x": "\"%1\" címke szerkesztése", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Biztosan törölni akarod a kijelölt címkéket?", "alerts.update-success": "Címke frissítve!" } \ No newline at end of file diff --git a/public/language/hu/admin/settings/advanced.json b/public/language/hu/admin/settings/advanced.json index f6cee0bc6d..4f7a584eb9 100644 --- a/public/language/hu/admin/settings/advanced.json +++ b/public/language/hu/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/hu/admin/settings/general.json b/public/language/hu/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/hu/admin/settings/general.json +++ b/public/language/hu/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/hu/admin/settings/group.json b/public/language/hu/admin/settings/group.json index 69aa385ba0..d59e515e26 100644 --- a/public/language/hu/admin/settings/group.json +++ b/public/language/hu/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Privát csoportok", "private-groups.help": "Ha engedélyezve van, a csoporthoz való csatlakozáshoz szükség van a csoport tulajdonosának jóváhagyására (Alapértelmezett: engedélyezve)", "private-groups.warning": "Vigyázat! Ha ez a lehetőség le van tiltva, és vannak privát csoportjaid, azok automatikusan nyilvánosak lesznek.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximális Csoportnév Hossz", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/hu/admin/settings/user.json b/public/language/hu/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/hu/admin/settings/user.json +++ b/public/language/hu/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json index 197c828e19..110966995d 100644 --- a/public/language/hu/modules.json +++ b/public/language/hu/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Legutóbbi csevegések", "chat.contacts": "Névjegyzék", "chat.message-history": "Üzenet előzmények", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Felugró csevegés", "chat.minimize": "Kis méret", diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json index 73e9dd1ea4..95c96c0d5e 100644 --- a/public/language/hu/topic.json +++ b/public/language/hu/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Add meg a témakör címét...", - "composer.handle_placeholder": "Név", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Elvet", "composer.submit": "Küldés", "composer.replying_to": "Válasz erre: %1", diff --git a/public/language/id/admin/development/info.json b/public/language/id/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/id/admin/development/info.json +++ b/public/language/id/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/id/admin/manage/tags.json b/public/language/id/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/id/admin/manage/tags.json +++ b/public/language/id/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/id/admin/settings/advanced.json b/public/language/id/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/id/admin/settings/advanced.json +++ b/public/language/id/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/id/admin/settings/general.json b/public/language/id/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/id/admin/settings/general.json +++ b/public/language/id/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/id/admin/settings/group.json b/public/language/id/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/id/admin/settings/group.json +++ b/public/language/id/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/id/admin/settings/user.json b/public/language/id/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/id/admin/settings/user.json +++ b/public/language/id/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/id/modules.json b/public/language/id/modules.json index 6cacb4ae4b..302a57c367 100644 --- a/public/language/id/modules.json +++ b/public/language/id/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Percakapan terbaru", "chat.contacts": "Kontak", "chat.message-history": "Riwayat Pesan", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Munculkan pesan", "chat.minimize": "Minimize", diff --git a/public/language/id/topic.json b/public/language/id/topic.json index 9d6cc654b5..2aa3188d73 100644 --- a/public/language/id/topic.json +++ b/public/language/id/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Masukkan judul topik di sini...", - "composer.handle_placeholder": "Nama", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Buang", "composer.submit": "Kirim", "composer.replying_to": "Membalas ke %1", diff --git a/public/language/it/admin/development/info.json b/public/language/it/admin/development/info.json index 5b30f67c5d..d121ea60a3 100644 --- a/public/language/it/admin/development/info.json +++ b/public/language/it/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Informazione - Tu sei su %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodi hanno risposto entro %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/it/admin/manage/tags.json b/public/language/it/admin/manage/tags.json index 529940c3c4..eaf74ce2c5 100644 --- a/public/language/it/admin/manage/tags.json +++ b/public/language/it/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Clicca qui per visitare la pagina impostazioni tag.", "name": "Nome Tag", - "alerts.editing-multiple": "Modifica di tag multipli", - "alerts.editing-x": "Modifica \"%1\" tag", + "alerts.editing": "Modifica tag(s)", "alerts.confirm-delete": "Vuoi eliminare i tag selezionati?", "alerts.update-success": "Tag aggiornato!" } \ No newline at end of file diff --git a/public/language/it/admin/settings/advanced.json b/public/language/it/admin/settings/advanced.json index 5f220d7593..78d1b53896 100644 --- a/public/language/it/admin/settings/advanced.json +++ b/public/language/it/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Rigorosa sicurezza trasporto", "hsts.enabled": "Abilita HSTS (consigliato)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Includi i sottodomini nell'intestazione HSTS", "hsts.preload": "Consenti la precarica dell'intestazione HSTS", "hsts.help": "Se abilitato, sarà impostata un'intestazione HSTS per questo sito. Puoi scegliere di includere sottodomini e segnalazioni di precaricamento nell'intestazione. In caso di dubbio, puoi lasciarle deselezionate. Più informazioni ", diff --git a/public/language/it/admin/settings/general.json b/public/language/it/admin/settings/general.json index 388e24ce82..1c13dc7774 100644 --- a/public/language/it/admin/settings/general.json +++ b/public/language/it/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Impostazioni Sito", "title": "Titolo Sito", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "L'URL del titolo del sito", "title.url-help": "Quando si clicca sul titolo, invia gli utenti a questo indirizzo. Se lasciato vuoto, l'utente sarà inviato all'indice del forum.", @@ -31,5 +33,9 @@ "outgoing-links": "Collegamenti in uscita", "outgoing-links.warning-page": "Usa pagina di avviso collegamenti in uscita", "search-default-sort-by": "Ricerca predefinita ordinata per", - "outgoing-links.whitelist": "Domini nella whitelist per aggirare la pagina di avviso" -} \ No newline at end of file + "outgoing-links.whitelist": "Domini nella whitelist per aggirare la pagina di avviso", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/it/admin/settings/group.json b/public/language/it/admin/settings/group.json index 2ddb45944e..cd53856149 100644 --- a/public/language/it/admin/settings/group.json +++ b/public/language/it/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Gruppi Privati", "private-groups.help": "Se abilitato, l'iscrizione ai gruppi richiede l'approvazione del proprietario del gruppo (Predefinito: abilitato)", "private-groups.warning": "Attenzione! Se questa opzione è disattivata e si hanno gruppi privati, questi diventano automaticamente pubblici.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Questo flag può essere usato per consentire agli utenti di selezionare più badge di gruppo, richiede il supporto del tema.", "max-name-length": "Lunghezza massima Nome Gruppo", "max-title-length": "Lunghezza massima Titolo Gruppo", diff --git a/public/language/it/admin/settings/user.json b/public/language/it/admin/settings/user.json index 3c151bf968..280c60fdcf 100644 --- a/public/language/it/admin/settings/user.json +++ b/public/language/it/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Solo Email", "account-settings": "Impostazioni Account", "gdpr_enabled": "Abilita la raccolta di consensi GDPR", - "gdpr_enabled_help": "Quando abilitato, tutti i soggetti in fase di registrazione dovranno dare il consenso esplicito per l'acquisizione e l'uso dei dati secondo la General Data Protection Regulation (GDPR). Nota: Attivando la GDPR gli utenti pre-esistenti non verranno forzati a dare il consenso. Per fare ciò, dovrai installare il plugin GDPR.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disabilita il cambio dello username", "disable-email-changes": "Disabilita il cambio di email", "disable-password-changes": "Disabilita cambio password", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Guardare", "categoryWatchState.notwatching": "Non Guardare", "categoryWatchState.ignoring": "Ignorato" -} \ No newline at end of file +} diff --git a/public/language/it/modules.json b/public/language/it/modules.json index 854698e4a5..9903368815 100644 --- a/public/language/it/modules.json +++ b/public/language/it/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Chat Recenti", "chat.contacts": "Contatti", "chat.message-history": "Cronologia Messaggi", + "chat.message-deleted": "Messaggio cancellato", "chat.options": "Opzioni chat", "chat.pop-out": "Chat in finestra", "chat.minimize": "Minimizza", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index be678682d9..0b86324ace 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Clicca sui post che vuoi spostare", "change_owner_instruction": "Clicca sui post che vuoi assegnare ad un altro utente", "composer.title_placeholder": "Inserisci qui il titolo della discussione...", - "composer.handle_placeholder": "Nome", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Annulla", "composer.submit": "Invia", "composer.replying_to": "Rispondendo a %1", diff --git a/public/language/ja/admin/development/info.json b/public/language/ja/admin/development/info.json index a04d316322..d3498dc7ec 100644 --- a/public/language/ja/admin/development/info.json +++ b/public/language/ja/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "お知らせ - あなたは%1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1ノードは%2ms以内に応答しました!", "host": "ホスト", "pid": "pid", diff --git a/public/language/ja/admin/manage/tags.json b/public/language/ja/admin/manage/tags.json index ae2b595a81..668e6c6ed7 100644 --- a/public/language/ja/admin/manage/tags.json +++ b/public/language/ja/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "こちらをクリックしてタグ設定ページにアクセスしてください。", "name": "タグ名", - "alerts.editing-multiple": "複数のタグを編集する", - "alerts.editing-x": "\"%1\"のタグを編集", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "選択したタグを削除しますか?", "alerts.update-success": "タグが更新されました!" } \ No newline at end of file diff --git a/public/language/ja/admin/settings/advanced.json b/public/language/ja/admin/settings/advanced.json index 6f4dc2702b..be9eb8cfda 100644 --- a/public/language/ja/admin/settings/advanced.json +++ b/public/language/ja/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "アクセス-制御-有効-ヘッダー", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/ja/admin/settings/general.json b/public/language/ja/admin/settings/general.json index f7c9e8dca7..dc61bfecee 100644 --- a/public/language/ja/admin/settings/general.json +++ b/public/language/ja/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "サイト設定", "title": "サイトタイトル", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "サイトタイトルのURL", "title.url-help": "タイトルをクリックすると、ユーザーをこのアドレスに送信します。空白のままにすると、ユーザーはフォーラムのインデックスに送信されます。", @@ -31,5 +33,9 @@ "outgoing-links": "外部サイトへのリンク", "outgoing-links.warning-page": "送信リンクの警告ページを使用", "search-default-sort-by": "デフォルトのソートを検索", - "outgoing-links.whitelist": "警告ページをバイパスするためのホワイトリストへのドメイン" -} \ No newline at end of file + "outgoing-links.whitelist": "警告ページをバイパスするためのホワイトリストへのドメイン", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/ja/admin/settings/group.json b/public/language/ja/admin/settings/group.json index 24bd3a76ed..d69f3a5421 100644 --- a/public/language/ja/admin/settings/group.json +++ b/public/language/ja/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "プライベートグループ", "private-groups.help": "有効の場合、グループへの参加はグループ管理人からの承認が必要です。(デフォルト: 有効)", "private-groups.warning": "注意!このオプションが無効で、プライベートグループがある場合、自動的に公開されます。", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "グループ名の最大文字数", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/ja/admin/settings/user.json b/public/language/ja/admin/settings/user.json index 1cb2afaaab..9bd19b6e31 100644 --- a/public/language/ja/admin/settings/user.json +++ b/public/language/ja/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "メールのみ", "account-settings": "アカウント設定", "gdpr_enabled": "GDPR同意収集を有効にする", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "ユーザー名の変更を無効にする", "disable-email-changes": "Eメールの変更を無効にする", "disable-password-changes": "パスワードの変更を無効にする", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "ウォッチ中", "categoryWatchState.notwatching": "未ウォッチ", "categoryWatchState.ignoring": "無視中" -} \ No newline at end of file +} diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json index 9cb03c10e9..b7cdbd6a1d 100644 --- a/public/language/ja/modules.json +++ b/public/language/ja/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "最近のチャット", "chat.contacts": "お問い合わせ", "chat.message-history": "メッセージ履歴", + "chat.message-deleted": "Message Deleted", "chat.options": "チャット設定", "chat.pop-out": "チャットを別ウィンドウで表示する", "chat.minimize": "最小化", diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index 6c2620eef2..5d05c10aec 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "移動したい投稿をクリック", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "スレッドのタイトルを入力...", - "composer.handle_placeholder": "名前", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "破棄する", "composer.submit": "保存する", "composer.replying_to": "%1へ返答中", diff --git a/public/language/ko/admin/development/info.json b/public/language/ko/admin/development/info.json index 078eeeef27..7c1d810a18 100644 --- a/public/language/ko/admin/development/info.json +++ b/public/language/ko/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "정보 - %1:%2에 있습니다.", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 노드가 %2ms 내로 응답했습니다.", "host": "호스트", "pid": "pid", diff --git a/public/language/ko/admin/manage/tags.json b/public/language/ko/admin/manage/tags.json index 2f744a3ca3..ecf0ea38d2 100644 --- a/public/language/ko/admin/manage/tags.json +++ b/public/language/ko/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "태그 설정 페이지를 방문하시려면 클릭하세요", "name": "태그 이름", - "alerts.editing-multiple": "다수의 태그 수정 중", - "alerts.editing-x": "\"%1\" 태그 수정 중", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "선택된 태그들을 삭제하시겠습니까?", "alerts.update-success": "태그가 업데이트 됐습니다! " } \ No newline at end of file diff --git a/public/language/ko/admin/settings/advanced.json b/public/language/ko/admin/settings/advanced.json index 428a298117..259db9e7a5 100644 --- a/public/language/ko/admin/settings/advanced.json +++ b/public/language/ko/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "HSTS 활성화 (권장)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/ko/admin/settings/general.json b/public/language/ko/admin/settings/general.json index 3b30ac4167..f4c442245c 100644 --- a/public/language/ko/admin/settings/general.json +++ b/public/language/ko/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "사이트 관리", "title": "사이트 제목", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "사이트 제목의 URL", "title.url-help": "제목을 클릭하면 사용자가 이 주소로 이동합니다. 비워두면 사용자가 포럼 인덱스로 이동합니다.", @@ -31,5 +33,9 @@ "outgoing-links": "외부 링크", "outgoing-links.warning-page": "외부 링크 경고페이지 사용", "search-default-sort-by": "검색결과 정열기준 기본값", - "outgoing-links.whitelist": "경고창이 필요없는 외부 링크 도메인(whitelist)" -} \ No newline at end of file + "outgoing-links.whitelist": "경고창이 필요없는 외부 링크 도메인(whitelist)", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/ko/admin/settings/group.json b/public/language/ko/admin/settings/group.json index 593a68487f..d00397af45 100644 --- a/public/language/ko/admin/settings/group.json +++ b/public/language/ko/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "비공개 그룹", "private-groups.help": " 활성화 되어있다면 그룹에 가입하는 것은 그룹 관리자의 허가를 필요로 합니다. (기본 설정: 활성화)", "private-groups.warning": "주의 이 옵션이 비활성화 돼있고 당신에게 비공개 그룹이 있다면 그 그룹들은 모두 공개로 전환될 것입니다.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "그룹 명 최대 길이", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/ko/admin/settings/user.json b/public/language/ko/admin/settings/user.json index f95520aae5..0c5ea1b415 100644 --- a/public/language/ko/admin/settings/user.json +++ b/public/language/ko/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "이메일", "account-settings": "계정 관리", "gdpr_enabled": "GDPR 동의 수집 활성화", - "gdpr_enabled_help": "활성화하면, 모든 새 가입자는 General Data Protection Regulation (GDPR) 하의 데이터 수집 및 사용에 대한 동의를 명시적으로 하도록 요구될 것입니다. 참고: GDPR을 활성화하는 것은 이전에 존재한 사용자에게 동의를 하도록 강제하지 않습니다. 그렇게 하려면, GDPR 플러그인을 설치해야 합니다.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "사용자명 변경 비활성화", "disable-email-changes": "이메일 주소 변경 비활성화", "disable-password-changes": "패스워드 변경 비활성화", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "주시하기", "categoryWatchState.notwatching": "주시하지 않기", "categoryWatchState.ignoring": "무시하기" -} \ No newline at end of file +} diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index d7e5dfaa45..4bf3334ce6 100644 --- a/public/language/ko/modules.json +++ b/public/language/ko/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "최근 채팅", "chat.contacts": "연락처", "chat.message-history": "대화 기록", + "chat.message-deleted": "Message Deleted", "chat.options": "채팅 옵션", "chat.pop-out": "분리된 창에서 채팅", "chat.minimize": "최소화", diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index 3efe67eb61..8aa5f8b716 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "게시물 제목을 입력하세요.", - "composer.handle_placeholder": "이름", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "취소", "composer.submit": "등록", "composer.replying_to": "'%1'에 대한 답글", diff --git a/public/language/lt/admin/development/info.json b/public/language/lt/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/lt/admin/development/info.json +++ b/public/language/lt/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/lt/admin/manage/tags.json b/public/language/lt/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/lt/admin/manage/tags.json +++ b/public/language/lt/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/lt/admin/settings/advanced.json b/public/language/lt/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/lt/admin/settings/advanced.json +++ b/public/language/lt/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/lt/admin/settings/general.json b/public/language/lt/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/lt/admin/settings/general.json +++ b/public/language/lt/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/lt/admin/settings/group.json b/public/language/lt/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/lt/admin/settings/group.json +++ b/public/language/lt/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/lt/admin/settings/user.json b/public/language/lt/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/lt/admin/settings/user.json +++ b/public/language/lt/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json index 3329aff5f6..5ec59448e2 100644 --- a/public/language/lt/modules.json +++ b/public/language/lt/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Paskutiniai susirašinėjimai", "chat.contacts": "Kontaktai", "chat.message-history": "Žinučių istorija", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Iššokančio lango pokalbiai", "chat.minimize": "Minimize", diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json index 677bafa53f..5255a43d62 100644 --- a/public/language/lt/topic.json +++ b/public/language/lt/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Įrašykite temos pavadinimą...", - "composer.handle_placeholder": "Vardas ir pavardė", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Atšaukti", "composer.submit": "Patvirtinti", "composer.replying_to": "Atsakymas %1", diff --git a/public/language/lv/admin/development/info.json b/public/language/lv/admin/development/info.json index 84ac184f72..48055ceec0 100644 --- a/public/language/lv/admin/development/info.json +++ b/public/language/lv/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - Tu lieto %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 serveri atbildēja %2ms laikā!", "host": "serveris", "pid": "pid", diff --git a/public/language/lv/admin/manage/tags.json b/public/language/lv/admin/manage/tags.json index 55417e306b..8938d777a6 100644 --- a/public/language/lv/admin/manage/tags.json +++ b/public/language/lv/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Noklikšķini, lai apmeklētu birku iestatījumu lapu.", "name": "Birkas nosaukums", - "alerts.editing-multiple": "Rediģēt vairākas birkas", - "alerts.editing-x": "Rediģēt \"%1\" birku", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Vai vēlies izdzēst šīs birkas?", "alerts.update-success": "Birka atjaunināta!" } \ No newline at end of file diff --git a/public/language/lv/admin/settings/advanced.json b/public/language/lv/admin/settings/advanced.json index adaebd71f7..f65007a295 100644 --- a/public/language/lv/admin/settings/advanced.json +++ b/public/language/lv/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "HTTP Strict Transport Security (HSTS)", "hsts.enabled": "Iespējots HSTS (ieteicams)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Iekļaut apakšdomēnus HSTS iezīmē", "hsts.preload": "Atļaut iepriekš ielādēt HSTS iezīmi", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/lv/admin/settings/general.json b/public/language/lv/admin/settings/general.json index 8398cd7dfc..e5dea395c5 100644 --- a/public/language/lv/admin/settings/general.json +++ b/public/language/lv/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Foruma iestatījumi", "title": "Foruma nosaukums", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "Foruma virsrakta URL", "title.url-help": "Kad virsraksts tiek noklikšķināts, nosūtīt uz šo adresi. Ja ir tukšs, nosūtīt uz foruma sākumlapu.", @@ -31,5 +33,9 @@ "outgoing-links": "Izejošās saites", "outgoing-links.warning-page": "Lietot izejošo saišu brīdinājuma lapu", "search-default-sort-by": "Noklusējuma kārtošana", - "outgoing-links.whitelist": "Domēni, kuriem apiet brīdinājuma lapu" -} \ No newline at end of file + "outgoing-links.whitelist": "Domēni, kuriem apiet brīdinājuma lapu", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/lv/admin/settings/group.json b/public/language/lv/admin/settings/group.json index 7cf9063096..22c037194b 100644 --- a/public/language/lv/admin/settings/group.json +++ b/public/language/lv/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Privātās grupas", "private-groups.help": "Pievienoties grupai nepieciešama grupas īpašnieka apstiprināšana (iespējots pēc noklusējama)", "private-groups.warning": "Ja šī opcija ir atspējota un privātās grupas ir iespējotas, tās automātiski kļūst par publiskām.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Atļaut lietotājiem izvēlēties vairākas grupu etiķetas, nepieciešams tēmas atbalsts.", "max-name-length": "Maksimālais grupas nosaukuma garums", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/lv/admin/settings/user.json b/public/language/lv/admin/settings/user.json index 2934d638f4..854269611a 100644 --- a/public/language/lv/admin/settings/user.json +++ b/public/language/lv/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Tikai ar e-pasta adresi", "account-settings": "Kontu iestatījumi", "gdpr_enabled": "Iespējot VDAR piekrišanas vākšanu", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Atspējot lietotājvārda izmaiņas", "disable-email-changes": "Atspējot e-pasta adreses izmaiņas", "disable-password-changes": "Atspējot paroles izmaiņas", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/lv/modules.json b/public/language/lv/modules.json index 61ce109d17..4cb9e75b57 100644 --- a/public/language/lv/modules.json +++ b/public/language/lv/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Nesenās sarunas", "chat.contacts": "Kontaktpersonas", "chat.message-history": "Sarunu vēsture", + "chat.message-deleted": "Message Deleted", "chat.options": "Sarunu iestatījumi", "chat.pop-out": "Uznirstošā saruna", "chat.minimize": "Minimizēt", diff --git a/public/language/lv/topic.json b/public/language/lv/topic.json index 8fa758f2d1..70615cba56 100644 --- a/public/language/lv/topic.json +++ b/public/language/lv/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Noklikšķini uz rakstiem, kurus pārvietot", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Ievadīt temata virsrakstu...", - "composer.handle_placeholder": "Nosaukums", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Atmest", "composer.submit": "Publicēt", "composer.replying_to": "Atbild %1", diff --git a/public/language/ms/admin/development/info.json b/public/language/ms/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/ms/admin/development/info.json +++ b/public/language/ms/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/ms/admin/manage/tags.json b/public/language/ms/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/ms/admin/manage/tags.json +++ b/public/language/ms/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/ms/admin/settings/advanced.json b/public/language/ms/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/ms/admin/settings/advanced.json +++ b/public/language/ms/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/ms/admin/settings/general.json b/public/language/ms/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/ms/admin/settings/general.json +++ b/public/language/ms/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/ms/admin/settings/group.json b/public/language/ms/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/ms/admin/settings/group.json +++ b/public/language/ms/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/ms/admin/settings/user.json b/public/language/ms/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/ms/admin/settings/user.json +++ b/public/language/ms/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json index 5c1a445514..bb970e5932 100644 --- a/public/language/ms/modules.json +++ b/public/language/ms/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Sembang Terbaru", "chat.contacts": "Hubungi", "chat.message-history": "Sejarah Pesanan", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop keluar sembang", "chat.minimize": "Minimize", diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json index 10f22cb9e3..891e76c511 100644 --- a/public/language/ms/topic.json +++ b/public/language/ms/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Masukkan tajuk topik disini", - "composer.handle_placeholder": "Nama", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Abaikan", "composer.submit": "Hantar", "composer.replying_to": "Balas ke %1", diff --git a/public/language/nb/admin/development/info.json b/public/language/nb/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/nb/admin/development/info.json +++ b/public/language/nb/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/nb/admin/manage/tags.json b/public/language/nb/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/nb/admin/manage/tags.json +++ b/public/language/nb/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/nb/admin/settings/advanced.json b/public/language/nb/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/nb/admin/settings/advanced.json +++ b/public/language/nb/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/nb/admin/settings/general.json b/public/language/nb/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/nb/admin/settings/general.json +++ b/public/language/nb/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/nb/admin/settings/group.json b/public/language/nb/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/nb/admin/settings/group.json +++ b/public/language/nb/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/nb/admin/settings/user.json b/public/language/nb/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/nb/admin/settings/user.json +++ b/public/language/nb/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json index d797b3dfa4..f6e984ae2e 100644 --- a/public/language/nb/modules.json +++ b/public/language/nb/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Nylige chatter", "chat.contacts": "Kontakter", "chat.message-history": "Meldingshistorikk", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop-out chat", "chat.minimize": "Minimize", diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index 829e937684..57bebcc951 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Skriv din tråd-tittel her", - "composer.handle_placeholder": "Navn", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Forkast", "composer.submit": "Send", "composer.replying_to": "Svarer i %1", diff --git a/public/language/nl/admin/development/info.json b/public/language/nl/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/nl/admin/development/info.json +++ b/public/language/nl/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/nl/admin/general/social.json b/public/language/nl/admin/general/social.json index 23aedfcfaa..85ed00c695 100644 --- a/public/language/nl/admin/general/social.json +++ b/public/language/nl/admin/general/social.json @@ -1,5 +1,5 @@ { - "post-sharing": "Post Sharing", - "info-plugins-additional": "Plugins can add additional networks for sharing posts.", - "save-success": "Successfully saved Post Sharing Networks!" + "post-sharing": "Berichten delen", + "info-plugins-additional": "Plug-ins kunnen extra netwerken toevoegen om berichten mee te delen.", + "save-success": "Netwerken om berichten te delen is succesvol opgeslagen!" } \ No newline at end of file diff --git a/public/language/nl/admin/manage/tags.json b/public/language/nl/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/nl/admin/manage/tags.json +++ b/public/language/nl/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/nl/admin/settings/advanced.json b/public/language/nl/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/nl/admin/settings/advanced.json +++ b/public/language/nl/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/nl/admin/settings/general.json b/public/language/nl/admin/settings/general.json index e6ddedc452..9e03ac0115 100644 --- a/public/language/nl/admin/settings/general.json +++ b/public/language/nl/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Instellingen", "title": "Site Titel", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "De URL van de site titel", "title.url-help": "Wanneer de titel word aangeklikt stuur gebruikers naar dit adress. Is het leeg gelaten dan worden gebruikers naar de forum hoofdpagina gestuurd.", @@ -31,5 +33,9 @@ "outgoing-links": "Uitgaande links", "outgoing-links.warning-page": "Gebruik waarschuwingspagina voor uitgaande links", "search-default-sort-by": "Standaard sortering voor zoeken", - "outgoing-links.whitelist": "Domeinen op de whitelist voor het omzeilen van de waarschuwingspagina" -} \ No newline at end of file + "outgoing-links.whitelist": "Domeinen op de whitelist voor het omzeilen van de waarschuwingspagina", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/nl/admin/settings/group.json b/public/language/nl/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/nl/admin/settings/group.json +++ b/public/language/nl/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/nl/admin/settings/user.json b/public/language/nl/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/nl/admin/settings/user.json +++ b/public/language/nl/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json index 98bbbd0dce..eed086eb8b 100644 --- a/public/language/nl/modules.json +++ b/public/language/nl/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Recent gevoerde gesprekken", "chat.contacts": "Contacten", "chat.message-history": "Berichtengeschiedenis", + "chat.message-deleted": "Bericht verwijderd", "chat.options": "Chat opties", "chat.pop-out": "Chatvenster opbrengen bij chat", "chat.minimize": "Verkleinen", diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index d5c3c3f496..3cfddfc6c1 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Klik op de berichten die je wilt verplaatsen", "change_owner_instruction": "Klik op de berichten die je wilt toewijzen aan een andere gebruiker", "composer.title_placeholder": "Voer hier de titel van het onderwerp in...", - "composer.handle_placeholder": "Naam", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Annuleren", "composer.submit": "Verzenden", "composer.replying_to": "Reactie op %1", diff --git a/public/language/pl/admin/development/info.json b/public/language/pl/admin/development/info.json index 1cf80f089f..51ba0e06c1 100644 --- a/public/language/pl/admin/development/info.json +++ b/public/language/pl/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Informacja - Jesteś na %1:%2", + "you-are-on": "Jesteś na %1:%2", + "ip": "IP %1", "nodes-responded": "%1 węzły odpowiedziały w ciągu %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/pl/admin/manage/digest.json b/public/language/pl/admin/manage/digest.json index 40437bb572..84f02ee239 100644 --- a/public/language/pl/admin/manage/digest.json +++ b/public/language/pl/admin/manage/digest.json @@ -1,13 +1,13 @@ { - "lead": "A listing of digest delivery stats and times is displayed below.", - "disclaimer": "Please be advised that email delivery is not guaranteed, due to the nature of email technology. Many variables factor into whether an email sent to the recipient server is ultimately delivered into the user's inbox, including server reputation, blacklisted IP addresses, and whether DKIM/SPF/DMARC is configured.", - "disclaimer-continued": "A successful delivery means the message was sent successfully by NodeBB and acknowledged by the recipient server. It does not mean the email landed in the inbox. For best results, we recommend using a third-party email delivery service such as SendGrid.", + "lead": "Wykaz statystyk i czasów podsumowań jest wyświetlony poniżej", + "disclaimer": "Proszę mieć na uwadze, że z natury tej technologii, dostarczenie wiadomości email nie jest gwarantowane. Wiele czynników ma wpływ na to, czy wiadomość wysłana na dany serwer ostatecznie trafi do skrzynki użytkownika, takich jak reputacja serwera, czarnej liście adresów IP i temu czy DKIM/SPF/DMARC jest skonfigurowane", + "disclaimer-continued": "Udana wysyłka oznacza, że wiadomość została wysłana przez NodeBB i otrzymane zostało potwierdzenie od serwera docelowego. Nie oznacza to jednak, że email dotarł do skrzynki użytkownika. Dla najlepszych rezultatów polecamy używać zewnętrznych usług dostarczania wiadomości email takich jak SendGrid", "user": "Użytkownik", "subscription": "Typ subskrypcji", - "last-delivery": "Last successful delivery", + "last-delivery": "Ostatnia udana wysyłka", "default": "Domyślne ustawienie systemowe", - "default-help": "System default means the user has not explicitly overridden the global forum setting for digests, which is currently: "%1"", + "default-help": "Domyślne ustawienia systemowe oznaczają, że użytkownik korzysta z globalnych ustawień podsumowań, czyli obecnie: "%1"", "resend": "Wyślij ponownie podsumowanie", "resend-all-confirm": "Czy na pewno chcesz ręcznie wykonać włącznie podsumowania?", "resent-single": "Ręczne wysyłanie podsumowania zakończone", diff --git a/public/language/pl/admin/manage/tags.json b/public/language/pl/admin/manage/tags.json index d2aea91ab6..d7a38207dd 100644 --- a/public/language/pl/admin/manage/tags.json +++ b/public/language/pl/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Kliknij tutaj, aby przejść do strony zarządzania tagami.", "name": "Nazwa taga", - "alerts.editing-multiple": "Edycja wielu tagów", - "alerts.editing-x": "Edytowanie tagu \"%1\"", + "alerts.editing": "edytowanie tagu/tagów", "alerts.confirm-delete": "Czy na pewno chcesz usunąć zaznaczone tagi?", "alerts.update-success": "Zaktualizowano tag-a!" } \ No newline at end of file diff --git a/public/language/pl/admin/settings/advanced.json b/public/language/pl/admin/settings/advanced.json index a85b0e1f89..c8e9b96277 100644 --- a/public/language/pl/admin/settings/advanced.json +++ b/public/language/pl/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Kontrola-Dostępu-Zezwól-Nagłówki", "hsts": "Strict Transport Security", "hsts.enabled": "Włączony HSTS (zalecane)", + "hsts.maxAge": "Maksymalny wiek HSTS", "hsts.subdomains": "Uwzględnij subdomeny w nagłówku HSTS", "hsts.preload": "Zezwól na wstępne ładowanie nagłówka HSTS", "hsts.help": "Jeśli ta opcja jest włączona, dla tej witryny zostanie ustawiony nagłówek HSTS. Możesz zdecydować, czy uwzględnić subdomeny i wstępnie ładować flagi w nagłówku. Jeśli masz wątpliwości, możesz zostawić te pola niezaznaczone. Więcej informacji", diff --git a/public/language/pl/admin/settings/general.json b/public/language/pl/admin/settings/general.json index 386ce862af..5a3df86c21 100644 --- a/public/language/pl/admin/settings/general.json +++ b/public/language/pl/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Ustawienia strony", "title": "Tytuł strony", + "title.short": "Krótki tytuł", + "title.short-placeholder": "Jeśli nie wskazano krótkiego tytułu, użyty zostanie tytuł strony", "title.url": "URL", "title.url-placeholder": "Adres URL strony tytułowej", "title.url-help": "Po kliknięciu w tytuł użytkownik przejdzie pod ten adres. Jeśli pole to jest puste, użytkownik zostanie przeniesiony na stronę główną forum.", @@ -31,5 +33,9 @@ "outgoing-links": "Odnośniki wychodzące", "outgoing-links.warning-page": "Używaj strony ostrzegawczej o odnośnikach wychodzących", "search-default-sort-by": "Domyślne sortowanie wyników wyszukiwania ", - "outgoing-links.whitelist": "Domeny na białej liście pozwalającej ominąć stronę ostrzegawczą" -} \ No newline at end of file + "outgoing-links.whitelist": "Domeny na białej liście pozwalającej ominąć stronę ostrzegawczą", + "site-colors": "Metadane kolorów strony", + "theme-color": "Kolor przewodni", + "background-color": "Kolor tła", + "background-color-help": "Kolor wykorzystywany jako tło ekranu ładowania gdy strona jest zainstalowana jako PWA" +} diff --git a/public/language/pl/admin/settings/group.json b/public/language/pl/admin/settings/group.json index f319a56c9f..9ffcccc941 100644 --- a/public/language/pl/admin/settings/group.json +++ b/public/language/pl/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Grupy prywatne", "private-groups.help": "Jeśli ta opcja jest włączona, dołączenie do grupy wymaga zatwierdzenia przez właściciela grupy (domyślnie: włączone)", "private-groups.warning": "Uwaga! Jeśli ta opcja jest wyłączona i masz prywatne grupy, automatycznie stają się one publiczne.", + "allow-multiple-badges": "Zezwól na korzystanie z wielu etykiet", "allow-multiple-badges-help": "Dzięki tej fladze użytkownicy mogą wybierać różne etykiety dla grup, w zależności od tematu.", "max-name-length": "Maksymalna długość nazwy grupy", "max-title-length": "Maksymalna długość tytułu grupy", diff --git a/public/language/pl/admin/settings/user.json b/public/language/pl/admin/settings/user.json index 0a2017680d..adee5b5205 100644 --- a/public/language/pl/admin/settings/user.json +++ b/public/language/pl/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Tylko adresu email", "account-settings": "Ustawienia konta", "gdpr_enabled": "Włącz gromadzenie danych (RODO)", - "gdpr_enabled_help": "Po włączeniu wszyscy nowi rejestrujący będą musieli jednoznacznie wyrazić zgodę na gromadzenie i wykorzystanie danych na mocy ogólnego rozporządzenia o ochronie danych (RODO). Uwaga: włączenie RODO nie zmusza wcześniejszych użytkowników do wyrażenia zgody. Aby to zrobić, musisz zainstalować wtyczkę GDPR.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Wyłącz możliwość zmiany nazwy użytkownika", "disable-email-changes": "Wyłącz możliwość zmiany adresu e-mail", "disable-password-changes": "Wyłącz możliwość zmiany hasła", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Obserwowane", "categoryWatchState.notwatching": "Nie obserwowane", "categoryWatchState.ignoring": "Ignorowane" -} \ No newline at end of file +} diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index 71803fa5ed..976a705819 100644 --- a/public/language/pl/modules.json +++ b/public/language/pl/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Ostatnie czaty", "chat.contacts": "Kontakty", "chat.message-history": "Historia wiadomości", + "chat.message-deleted": "Wiadomość usunięta", "chat.options": "Ustawienia czatu", "chat.pop-out": "Otwórz czat w nowym oknie", "chat.minimize": "Minimalizuj", diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 45f744e20d..0fd5ac7593 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Zaznacz posty, które chcesz przenieść", "change_owner_instruction": "Kliknij w posty, które chcesz przypisać do innego użytkownika", "composer.title_placeholder": "Tutaj wpisz tytuł tematu...", - "composer.handle_placeholder": "Nazwa", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Odrzuć", "composer.submit": "Utwórz", "composer.replying_to": "Odpowiedź na %1", diff --git a/public/language/pt-BR/admin/development/info.json b/public/language/pt-BR/admin/development/info.json index 277aae3210..f9dc28a53e 100644 --- a/public/language/pt-BR/admin/development/info.json +++ b/public/language/pt-BR/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Informação - Você está em %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes respondidos dentro de %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/pt-BR/admin/manage/tags.json b/public/language/pt-BR/admin/manage/tags.json index b1382bba57..620f46de8c 100644 --- a/public/language/pt-BR/admin/manage/tags.json +++ b/public/language/pt-BR/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Clique aqui para visitar a página de configurações de tag.", "name": "Nome da Tag", - "alerts.editing-multiple": "Editando múltiplas tags", - "alerts.editing-x": "Editando a Tag \"%1\"", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Você deseja excluir as tags selecionadas?", "alerts.update-success": "Tag Atualizada!" } \ No newline at end of file diff --git a/public/language/pt-BR/admin/settings/advanced.json b/public/language/pt-BR/admin/settings/advanced.json index 60876ce8a5..9404ac18a2 100644 --- a/public/language/pt-BR/admin/settings/advanced.json +++ b/public/language/pt-BR/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Habilitar HSTS (recomendado)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Incluir subdomínios no cabeçalho do HSTS", "hsts.preload": "Permitir pré-carregamento do cabeçalho do HSTS", "hsts.help": "Se habilitado, um cabeçalho de HSTS será enviado para este site. Você pode selecionar tanto quais subdomínios deseja incluir, como quais serão as flags de pré-carregamento no seu cabeçalho. Se estiver em dúvida, você pode deixar esta opção desmarcada. Mais informações", diff --git a/public/language/pt-BR/admin/settings/general.json b/public/language/pt-BR/admin/settings/general.json index c41c158d02..74c2a4e7df 100644 --- a/public/language/pt-BR/admin/settings/general.json +++ b/public/language/pt-BR/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Configurações do Site", "title": "Título do Site", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "A URL do título do site", "title.url-help": "Quando o título é clicado, enviar os usuários para este endereço. Se deixado em branco, o usuário será enviado para a página inicial do fórum.", @@ -31,5 +33,9 @@ "outgoing-links": "Links Externos", "outgoing-links.warning-page": "Habilitar Página de Aviso de Links Externos", "search-default-sort-by": "Padrão de ordenação de pesquisa por", - "outgoing-links.whitelist": "Domínios que não receberão o aviso de link externo quando clicados" -} \ No newline at end of file + "outgoing-links.whitelist": "Domínios que não receberão o aviso de link externo quando clicados", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/pt-BR/admin/settings/group.json b/public/language/pt-BR/admin/settings/group.json index 063420edf3..476ed36bbc 100644 --- a/public/language/pt-BR/admin/settings/group.json +++ b/public/language/pt-BR/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Grupos Privados", "private-groups.help": "Se habilitado, a entrada nos grupos exigirá a apovação do dono do grupo (Padrão: ligado)", "private-groups.warning": "Atenção! Se esta opção estiver desabilitada e você tiver grupos privados, eles automaticamente se tornarão públicos.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Esta opção pode ser usada para permitir que os usuários selecionem várias insígnias de grupo, requer suporte ao tema.", "max-name-length": "Tamanho Máximo do Nome do Grupo", "max-title-length": "Tamanho Máximo do Título do Grupo", diff --git a/public/language/pt-BR/admin/settings/user.json b/public/language/pt-BR/admin/settings/user.json index 8f2dd52120..0f68d83c52 100644 --- a/public/language/pt-BR/admin/settings/user.json +++ b/public/language/pt-BR/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Apenas E-mail", "account-settings": "Configurações de Conta", "gdpr_enabled": "Ativar coleta de consentimento do GDPR", - "gdpr_enabled_help": "Quando ativado, todos os novos usuários que se cadastrarem devem autorizar explicitamente a coleta e o uso de dados de acordo com o Regulamento Geral sobre a Proteção de Dados (GDPR). Atenção: ao habilitar a requisição de autorização de acordo com o GDPR, não força os usuários já cadastrados a autorizar a coleta e o uso de dados. Para isso, você precisa instalar o plugin GDPR.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Desabilitar mudança de nome de usuário", "disable-email-changes": "Desabilitar mudanças de e-mail", "disable-password-changes": "Desabilitar mudanças de senha", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Acompanhando", "categoryWatchState.notwatching": "Não Acompanhar", "categoryWatchState.ignoring": "Ignorar" -} \ No newline at end of file +} diff --git a/public/language/pt-BR/modules.json b/public/language/pt-BR/modules.json index d80c28cfc4..19ab6e0ea6 100644 --- a/public/language/pt-BR/modules.json +++ b/public/language/pt-BR/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Conversas Recentes", "chat.contacts": "Contatos", "chat.message-history": "Histórico de Mensagens", + "chat.message-deleted": "Message Deleted", "chat.options": "Opções da conversa", "chat.pop-out": "Pop-out o chat", "chat.minimize": "Minimizar", diff --git a/public/language/pt-BR/topic.json b/public/language/pt-BR/topic.json index 40a547cc47..928917a2bf 100644 --- a/public/language/pt-BR/topic.json +++ b/public/language/pt-BR/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Clique nas postagens que você deseja mover", "change_owner_instruction": "Clique na postagem que você quer associar a outro usuário", "composer.title_placeholder": "Digite aqui o título para o seu tópico...", - "composer.handle_placeholder": "Nome", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Descartar", "composer.submit": "Enviar", "composer.replying_to": "Respondendo para %1", diff --git a/public/language/pt-PT/admin/development/info.json b/public/language/pt-PT/admin/development/info.json index 2dbf2dd99f..c1038cb872 100644 --- a/public/language/pt-PT/admin/development/info.json +++ b/public/language/pt-PT/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/pt-PT/admin/manage/tags.json b/public/language/pt-PT/admin/manage/tags.json index 034cf190e0..b46abe4745 100644 --- a/public/language/pt-PT/admin/manage/tags.json +++ b/public/language/pt-PT/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Nome da Etiqueta", - "alerts.editing-multiple": "Editar múltiplos marcadores", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Queres mesmo apagar os marcadores selecionados?", "alerts.update-success": "Etiqueta Atualizada!" } \ No newline at end of file diff --git a/public/language/pt-PT/admin/settings/advanced.json b/public/language/pt-PT/admin/settings/advanced.json index fb03a0c0e4..f2f94db329 100644 --- a/public/language/pt-PT/admin/settings/advanced.json +++ b/public/language/pt-PT/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/pt-PT/admin/settings/general.json b/public/language/pt-PT/admin/settings/general.json index fdd9ceee78..23afdf2329 100644 --- a/public/language/pt-PT/admin/settings/general.json +++ b/public/language/pt-PT/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/pt-PT/admin/settings/group.json b/public/language/pt-PT/admin/settings/group.json index a3d2bcf670..044ec0b94d 100644 --- a/public/language/pt-PT/admin/settings/group.json +++ b/public/language/pt-PT/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Grupos Privados", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Cuidado! Se esta opção estiver desativada e tu tiveres grupos privados, eles automaticamente vão se tornar públicos.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Comprimento Máximo do Nome do Grupo", "max-title-length": "Comprimento Máximo do Título do Grupo", diff --git a/public/language/pt-PT/admin/settings/user.json b/public/language/pt-PT/admin/settings/user.json index aea281e945..c97a6e9e96 100644 --- a/public/language/pt-PT/admin/settings/user.json +++ b/public/language/pt-PT/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Apenas E-mail", "account-settings": "Definições de Conta", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Desativar mudanças de nomes de utilizador", "disable-email-changes": "Desativar mudanças de e-mail", "disable-password-changes": "Desativar alterações de palavras-passe", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/pt-PT/modules.json b/public/language/pt-PT/modules.json index 27231fa137..6204a3c627 100644 --- a/public/language/pt-PT/modules.json +++ b/public/language/pt-PT/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Conversas recentes", "chat.contacts": "Contactos", "chat.message-history": "Histórico de mensagens", + "chat.message-deleted": "Message Deleted", "chat.options": "Opções de conversa", "chat.pop-out": "Destacar a janela de conversação", "chat.minimize": "Minimize", diff --git a/public/language/pt-PT/topic.json b/public/language/pt-PT/topic.json index 28ab05c548..5290bca071 100644 --- a/public/language/pt-PT/topic.json +++ b/public/language/pt-PT/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Clica nas publicações que queres mover", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Insere aqui o título do tópico...", - "composer.handle_placeholder": "Nome", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Descartar", "composer.submit": "Publicar", "composer.replying_to": "Respondendo a %1", diff --git a/public/language/ro/admin/development/info.json b/public/language/ro/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/ro/admin/development/info.json +++ b/public/language/ro/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/ro/admin/manage/tags.json b/public/language/ro/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/ro/admin/manage/tags.json +++ b/public/language/ro/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/ro/admin/settings/advanced.json b/public/language/ro/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/ro/admin/settings/advanced.json +++ b/public/language/ro/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/ro/admin/settings/general.json b/public/language/ro/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/ro/admin/settings/general.json +++ b/public/language/ro/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/ro/admin/settings/group.json b/public/language/ro/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/ro/admin/settings/group.json +++ b/public/language/ro/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/ro/admin/settings/user.json b/public/language/ro/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/ro/admin/settings/user.json +++ b/public/language/ro/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json index 448e1503ea..1648980bac 100644 --- a/public/language/ro/modules.json +++ b/public/language/ro/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Conversații Recente", "chat.contacts": "Contacte", "chat.message-history": "Istorie Mesaje", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Chat pop-up", "chat.minimize": "Minimize", diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index c28f84bece..7f522b36d0 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Introdu numele subiectului aici ...", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Renunță", "composer.submit": "Trimite", "composer.replying_to": "Îi raspunde lui %1", diff --git a/public/language/ru/admin/development/info.json b/public/language/ru/admin/development/info.json index a0250a743a..d3b728a01f 100644 --- a/public/language/ru/admin/development/info.json +++ b/public/language/ru/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Вы находитесь на %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "Узлов: %1. Время ответа %2мс!", "host": "хост", "pid": "pid", diff --git a/public/language/ru/admin/manage/tags.json b/public/language/ru/admin/manage/tags.json index db20d901f7..d210e558c2 100644 --- a/public/language/ru/admin/manage/tags.json +++ b/public/language/ru/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Нажмите сюда чтобы перейти на страницу настройки меток.", "name": "Название метки", - "alerts.editing-multiple": "Редактирование нескольких меток", - "alerts.editing-x": "Редактирование метки \"%1\"", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Вы хотите удалить выбранные метки?", "alerts.update-success": "Метка обновлена!" } \ No newline at end of file diff --git a/public/language/ru/admin/settings/advanced.json b/public/language/ru/admin/settings/advanced.json index 6f9e590477..454c4442b0 100644 --- a/public/language/ru/admin/settings/advanced.json +++ b/public/language/ru/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Строгая политика безопасности транспортного уровня", "hsts.enabled": "Включить HSTS (рекомендуется)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Включать в заголовок HSTS поддомены", "hsts.preload": "Разрешить предзагрузку заголовка HSTS", "hsts.help": "Включите, чтобы установить заголовок HSTS для этого сайта, а также настроить его предзагрузку или использование поддоменов. Если вы не уверены, какими должны быть эти параметры, оставьте всё как есть. Дополнительная информация ", diff --git a/public/language/ru/admin/settings/general.json b/public/language/ru/admin/settings/general.json index 1895e3ad88..047d09926a 100644 --- a/public/language/ru/admin/settings/general.json +++ b/public/language/ru/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Настройки сайта", "title": "Название сайта", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "URL для названия сайта", "title.url-help": "Когда пользователь нажмёт на название сайта, он перейдёт по этой ссылке.\nОставьте поле пустым, чтобы отправлять пользователей на главную страницу форума.", @@ -31,5 +33,9 @@ "outgoing-links": "Внешние ссылки", "outgoing-links.warning-page": "Предупреждать, когда пользователь переходит по внешним ссылкам", "search-default-sort-by": "Сортировать результаты поиска по", - "outgoing-links.whitelist": "Список доменов, для которых страница предупреждения отключена" -} \ No newline at end of file + "outgoing-links.whitelist": "Список доменов, для которых страница предупреждения отключена", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/ru/admin/settings/group.json b/public/language/ru/admin/settings/group.json index e09b9d025f..f7485f8d71 100644 --- a/public/language/ru/admin/settings/group.json +++ b/public/language/ru/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Закрытые группы", "private-groups.help": "Когда эта настройка включена, присоединиться к группе можно только с разрешения её владельца (Включено по умолчанию)", "private-groups.warning": "Внимание! Если вы отключите эту опцию, все закрытые группы автоматически станут открытыми.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Разрешить пользователям выбирать несколько значков групп (для полноценной работы этой функции требуется её поддержка в теме оформления форума).", "max-name-length": "Максимальная длина названия группы", "max-title-length": "Максимальная длина звания участника группы", diff --git a/public/language/ru/admin/settings/user.json b/public/language/ru/admin/settings/user.json index 6c4ca13439..e21c0cc55e 100644 --- a/public/language/ru/admin/settings/user.json +++ b/public/language/ru/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Только адреса электронной почты", "account-settings": "Настройки учётной записи", "gdpr_enabled": "Запрашивать согласие на сбор и обработку персональных данных", - "gdpr_enabled_help": "Включите, чтобы при регистрации новых пользователей запрашивать согласие на сбор и обработку данных в соответствии с законом о General Data Protection Regulation (GDPR). Примечание: эта настойка не повлияет на уже зарегистрированных пользователей. Чтобы запросить их согласие, установите плагин GDPR.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Запретить смену имени пользователя", "disable-email-changes": "Запретить смену адреса электронной почты", "disable-password-changes": "Запретить смену пароля", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Отслеживается", "categoryWatchState.notwatching": "Не отслеживается", "categoryWatchState.ignoring": "Игнорируется" -} \ No newline at end of file +} diff --git a/public/language/ru/category.json b/public/language/ru/category.json index fbd551fd69..9efe7d1454 100644 --- a/public/language/ru/category.json +++ b/public/language/ru/category.json @@ -16,7 +16,7 @@ "not-watching.description": "Не показывать темы из этой категории в непрочитанных, но оставить в списке недавних", "ignoring.description": "Не показывать темы из этой категории ни в списке непрочитанных, ни в недавних", "watching.message": "Вы отслеживаете обновления этой категории, включая все подкатегории", - "notwatching.message": "You are not watching updates from this category and all subcategories", - "ignoring.message": "You are now ignoring updates from this category and all subcategories", + "notwatching.message": "Вы более не отслеживаете обновления этой категории, включая все подкатегории", + "ignoring.message": "Вы игнорируете обновления этой категории, включая все подкатегории", "watched-categories": "Отслеживаемые категории" } \ No newline at end of file diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json index c3b09ce9fc..c54b5a8d78 100644 --- a/public/language/ru/modules.json +++ b/public/language/ru/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Последние переписки", "chat.contacts": "Контакты", "chat.message-history": "История сообщений", + "chat.message-deleted": "Сообщение удалено", "chat.options": "Опции чата", "chat.pop-out": "Покинуть диалог", "chat.minimize": "Свернуть", @@ -34,9 +35,9 @@ "chat.kick": "Исключить", "chat.show-ip": "Показать IP", "chat.owner": "Владелец комнаты", - "chat.system.user-join": "%1 has joined the room", - "chat.system.user-leave": "%1 has left the room", - "chat.system.room-rename": "%2 has renamed this room: %1", + "chat.system.user-join": "%1 присоединился к беседе", + "chat.system.user-leave": "%1 покинул беседу", + "chat.system.room-rename": "%2 переименовал беседу: %1", "composer.compose": "Редактор сообщений", "composer.show_preview": "Показать предпросмотр сообщения", "composer.hide_preview": "Скрыть предпросмотр", diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index d26318613d..57a0844317 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Нажмите на сообщения, которые вы хотите перенести", "change_owner_instruction": "Нажмите на сообщения, которые вы хотите присвоить другому пользователю", "composer.title_placeholder": "Введите название темы...", - "composer.handle_placeholder": "Название", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Отменить", "composer.submit": "Отправить", "composer.replying_to": "Ответ %1", diff --git a/public/language/rw/admin/development/info.json b/public/language/rw/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/rw/admin/development/info.json +++ b/public/language/rw/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/rw/admin/manage/tags.json b/public/language/rw/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/rw/admin/manage/tags.json +++ b/public/language/rw/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/rw/admin/settings/advanced.json b/public/language/rw/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/rw/admin/settings/advanced.json +++ b/public/language/rw/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/rw/admin/settings/general.json b/public/language/rw/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/rw/admin/settings/general.json +++ b/public/language/rw/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/rw/admin/settings/group.json b/public/language/rw/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/rw/admin/settings/group.json +++ b/public/language/rw/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/rw/admin/settings/user.json b/public/language/rw/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/rw/admin/settings/user.json +++ b/public/language/rw/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json index 7dec469b84..0f77802527 100644 --- a/public/language/rw/modules.json +++ b/public/language/rw/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Ubutumwa Buheruka", "chat.contacts": "Abo Kuvugisha", "chat.message-history": "Ubutumwa Bwahise", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Fungura Akadirishya k'Igikari", "chat.minimize": "Minimize", diff --git a/public/language/rw/topic.json b/public/language/rw/topic.json index 34bdbc135c..25cc8e1bc3 100644 --- a/public/language/rw/topic.json +++ b/public/language/rw/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Shyira umutwe w'ikiganiro cyawe aha...", - "composer.handle_placeholder": "Izina", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Byihorere", "composer.submit": "Shyiraho", "composer.replying_to": "Gusubiza %1", diff --git a/public/language/sc/admin/development/info.json b/public/language/sc/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/sc/admin/development/info.json +++ b/public/language/sc/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/sc/admin/manage/tags.json b/public/language/sc/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/sc/admin/manage/tags.json +++ b/public/language/sc/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/sc/admin/settings/advanced.json b/public/language/sc/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/sc/admin/settings/advanced.json +++ b/public/language/sc/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/sc/admin/settings/general.json b/public/language/sc/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/sc/admin/settings/general.json +++ b/public/language/sc/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/sc/admin/settings/group.json b/public/language/sc/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/sc/admin/settings/group.json +++ b/public/language/sc/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/sc/admin/settings/user.json b/public/language/sc/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/sc/admin/settings/user.json +++ b/public/language/sc/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json index 2f4d6efee1..3a9616ea46 100644 --- a/public/language/sc/modules.json +++ b/public/language/sc/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Recent Chats", "chat.contacts": "Contacts", "chat.message-history": "Message History", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out chat", "chat.minimize": "Minimize", diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json index d13fbad9d0..8bdb1f72c6 100644 --- a/public/language/sc/topic.json +++ b/public/language/sc/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Pone su tìtulu de s'arresonada inoghe...", - "composer.handle_placeholder": "Name", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Lassa a Pèrdere", "composer.submit": "Imbia", "composer.replying_to": "Replying to %1", diff --git a/public/language/sk/admin/development/info.json b/public/language/sk/admin/development/info.json index de652f640a..a150eb754d 100644 --- a/public/language/sk/admin/development/info.json +++ b/public/language/sk/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Informácie - Nachádzate sa na %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 väzieb odpovedalo počas %2ms.", "host": "hosť", "pid": "pid", diff --git a/public/language/sk/admin/manage/tags.json b/public/language/sk/admin/manage/tags.json index a6a4f598e7..e05e18dfb7 100644 --- a/public/language/sk/admin/manage/tags.json +++ b/public/language/sk/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "K prejdeniu na stránku s nastavením značiek, kliknite sem.", "name": "Názov značky", - "alerts.editing-multiple": "Úprava viacerých značiek", - "alerts.editing-x": "Úprava značky \"%1\"", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Chcete odstrániť vybranú značku?", "alerts.update-success": "Značka bola aktualizovaná!" } \ No newline at end of file diff --git a/public/language/sk/admin/settings/advanced.json b/public/language/sk/admin/settings/advanced.json index 9774a719a1..137c931300 100644 --- a/public/language/sk/admin/settings/advanced.json +++ b/public/language/sk/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Prísne zabezpečenie prenosu", "hsts.enabled": "Povoliť HSTS (odporúčané)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Zahrnúť pod domény v hlavičke HSTS", "hsts.preload": "Povoliť pred načítavanie hlavičky HSTS", "hsts.help": "Ak je povolené, bude nastavená pre tieto stránky hlavička HSTS. V hlavičke si môžete zvoliť aj zahrnutie pod domén a prednastavených príznakov. Ak si nieste istý, nechajte nezaškrtnuté Viac informácií ", diff --git a/public/language/sk/admin/settings/general.json b/public/language/sk/admin/settings/general.json index 74d40ce6a2..8948eed9a0 100644 --- a/public/language/sk/admin/settings/general.json +++ b/public/language/sk/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Nastavenia stránky", "title": "Názov stránky", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "URL názov stránky", "title.url-help": "Ak bude kliknuté na názov, používateľ bude presmerovaný na túto adresu. Ak zostane prázdne, užívateľ bude odoslaný na index fóra.", @@ -31,5 +33,9 @@ "outgoing-links": "Odchádzajúce odkazy", "outgoing-links.warning-page": "Použiť stránku s upozornením pri odchádzajúcich odkazoch", "search-default-sort-by": "Predvolené zoradenie pri hľadaní", - "outgoing-links.whitelist": "Domény u ktorých bude preskočená upozorňovacia stránka" -} \ No newline at end of file + "outgoing-links.whitelist": "Domény u ktorých bude preskočená upozorňovacia stránka", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/sk/admin/settings/group.json b/public/language/sk/admin/settings/group.json index 5503544eb8..438d6a73c6 100644 --- a/public/language/sk/admin/settings/group.json +++ b/public/language/sk/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Súkromné ​​skupiny", "private-groups.help": "Ak je povolené, pripojenie k skupine vyžaduje schválenie zakladateľa skupiny (Predvolené: povolené)", "private-groups.warning": "Ale pozor, ak je táto možnosť zakázaná a vy máte súkromné ​​skupiny, stanú sa automaticky verejnými.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Toto označenie môže byť použité, aby používatelia mohli vybrať niekoľko skupinových symbolov, vyžaduj podporu motívov.", "max-name-length": "Maximálna dĺžka názvu skupiny", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/sk/admin/settings/user.json b/public/language/sk/admin/settings/user.json index 992c76786d..5450ec2d0d 100644 --- a/public/language/sk/admin/settings/user.json +++ b/public/language/sk/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Iba e-mail", "account-settings": "Nastavenia účtu", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Zakázať zmenu používateľského mena", "disable-email-changes": "Zakázať zmenu e-mailu", "disable-password-changes": "Zakázať zmenu hesla", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json index 3d7ea2d108..5c0f7fa712 100644 --- a/public/language/sk/modules.json +++ b/public/language/sk/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Najnovšie rozhovory", "chat.contacts": "Kontakty", "chat.message-history": "História správ", + "chat.message-deleted": "Message Deleted", "chat.options": "Možnosti konverzácie", "chat.pop-out": "Vyskakujúce okno konverzácie", "chat.minimize": "Minimalizovať", diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json index 2838a4a78b..adaaa97622 100644 --- a/public/language/sk/topic.json +++ b/public/language/sk/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Kliknite na príspevky, ktoré chcete presunúť", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Sem zadajte názov témy...", - "composer.handle_placeholder": "Meno", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Zahodiť", "composer.submit": "Odoslať", "composer.replying_to": "Odpovedať na %1", diff --git a/public/language/sl/admin/development/info.json b/public/language/sl/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/sl/admin/development/info.json +++ b/public/language/sl/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/sl/admin/manage/tags.json b/public/language/sl/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/sl/admin/manage/tags.json +++ b/public/language/sl/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/sl/admin/settings/advanced.json b/public/language/sl/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/sl/admin/settings/advanced.json +++ b/public/language/sl/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/sl/admin/settings/general.json b/public/language/sl/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/sl/admin/settings/general.json +++ b/public/language/sl/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/sl/admin/settings/group.json b/public/language/sl/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/sl/admin/settings/group.json +++ b/public/language/sl/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/sl/admin/settings/user.json b/public/language/sl/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/sl/admin/settings/user.json +++ b/public/language/sl/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json index 9ac499d691..a9cc2928d9 100644 --- a/public/language/sl/modules.json +++ b/public/language/sl/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Zadnji klepeti", "chat.contacts": "Stiki", "chat.message-history": "Zgodovina klepeta", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Klepet v novem oknu", "chat.minimize": "Minimize", diff --git a/public/language/sl/topic.json b/public/language/sl/topic.json index 95413dbc1a..24c83d4009 100644 --- a/public/language/sl/topic.json +++ b/public/language/sl/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "Vpiši naslov teme...", - "composer.handle_placeholder": "Ime", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Zavrzi", "composer.submit": "Pošlji", "composer.replying_to": "Odgovor na %1", diff --git a/public/language/sr/admin/development/info.json b/public/language/sr/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/sr/admin/development/info.json +++ b/public/language/sr/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/sr/admin/manage/tags.json b/public/language/sr/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/sr/admin/manage/tags.json +++ b/public/language/sr/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/sr/admin/settings/advanced.json b/public/language/sr/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/sr/admin/settings/advanced.json +++ b/public/language/sr/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/sr/admin/settings/general.json b/public/language/sr/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/sr/admin/settings/general.json +++ b/public/language/sr/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/sr/admin/settings/group.json b/public/language/sr/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/sr/admin/settings/group.json +++ b/public/language/sr/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/sr/admin/settings/user.json b/public/language/sr/admin/settings/user.json index 66e3bd09cc..f6f4be69d3 100644 --- a/public/language/sr/admin/settings/user.json +++ b/public/language/sr/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Samo Email", "account-settings": "Podešavanje naloga", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Onemogući promenu korisničkog imena", "disable-email-changes": "Onemogući promenu email-a", "disable-password-changes": "Onemogući promenu šifre", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/sr/global.json b/public/language/sr/global.json index 4277511fd4..ef6f5fd879 100644 --- a/public/language/sr/global.json +++ b/public/language/sr/global.json @@ -12,9 +12,9 @@ "400.title": "Неисправан захтев.", "400.message": "Изгледа да је веза погрешно уобличена, проверите и пробајте поново. У супротном, вратите се на почетну страницу.", "register": "Регистрација", - "login": "Пријава", + "login": "Пријави се", "please_log_in": "Молимо, пријавите се", - "logout": "Одјава", + "logout": "Одјави се", "posting_restriction_info": "Слање порука је тренутно ограничено само на пријављене кориснике, кликните овде да се пријавите.", "welcome_back": "Добродошли поново", "you_have_successfully_logged_in": "Успешно сте се пријавили", diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json index 061fe75dfe..db060676e2 100644 --- a/public/language/sr/modules.json +++ b/public/language/sr/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Недавна ћаскања", "chat.contacts": "Контакти", "chat.message-history": "Историја порука", + "chat.message-deleted": "Порука је избрисана", "chat.options": "Опције ћаскања", "chat.pop-out": "Истакни ћаскање", "chat.minimize": "Умањи", diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json index dae40bddb6..2050d4d02b 100644 --- a/public/language/sr/topic.json +++ b/public/language/sr/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Кликните на поруке које желите да преместите", "change_owner_instruction": "Кликните на поруке које желите да доделите другом кориснику", "composer.title_placeholder": "Овде унесите назив теме...", - "composer.handle_placeholder": "Име", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Одбаци", "composer.submit": "Пошаљи", "composer.replying_to": "Писање одговора на %1", diff --git a/public/language/sv/admin/admin.json b/public/language/sv/admin/admin.json index 36f0fdde60..7a057d636c 100644 --- a/public/language/sv/admin/admin.json +++ b/public/language/sv/admin/admin.json @@ -1,7 +1,7 @@ { - "alert.confirm-rebuild-and-restart": "Are you sure you wish to rebuild and restart NodeBB?", - "alert.confirm-restart": "Are you sure you wish to restart NodeBB?", + "alert.confirm-rebuild-and-restart": "Är du säker på att du vill bygga och starta om NodeBB?", + "alert.confirm-restart": "Är du säker på att du vill starta om NodeBB?", - "acp-title": "%1 | NodeBB Admin Control Panel", - "settings-header-contents": "Contents" + "acp-title": "%1 | NodeBB Admin Kontrollpanel", + "settings-header-contents": "Innehåll" } \ No newline at end of file diff --git a/public/language/sv/admin/advanced/cache.json b/public/language/sv/admin/advanced/cache.json index 5a954f1232..e2e2536bb2 100644 --- a/public/language/sv/admin/advanced/cache.json +++ b/public/language/sv/admin/advanced/cache.json @@ -2,7 +2,7 @@ "post-cache": "Post Cache", "posts-in-cache": "Posts in Cache", "average-post-size": "Average Post Size", - "length-to-max": "Length / Max", + "length-to-max": "Längd / Max", "percent-full": "%1% Full", "post-cache-size": "Post Cache Size", "items-in-cache": "Items in Cache", diff --git a/public/language/sv/admin/development/info.json b/public/language/sv/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/sv/admin/development/info.json +++ b/public/language/sv/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/sv/admin/manage/tags.json b/public/language/sv/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/sv/admin/manage/tags.json +++ b/public/language/sv/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/sv/admin/settings/advanced.json b/public/language/sv/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/sv/admin/settings/advanced.json +++ b/public/language/sv/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/sv/admin/settings/email.json b/public/language/sv/admin/settings/email.json index cfdcc04a5b..f331876cbe 100644 --- a/public/language/sv/admin/settings/email.json +++ b/public/language/sv/admin/settings/email.json @@ -4,9 +4,9 @@ "address-help": "The following email address refers to the email that the recipient will see in the \"From\" and \"Reply To\" fields.", "from": "From Name", "from-help": "The from name to display in the email.", - "sendmail-rate-limit": "Send X emails...", - "sendmail-rate-delta": "... every X milliseconds", - "sendmail-rate-help": "Instructs the NodeBB mailer to limit the number of messages sent at once in order to not overwhelm email receiving services. These options do not apply if SMTP Transport is enabled (below).", + "sendmail-rate-limit": "Skicka X epostmeddelanden...", + "sendmail-rate-delta": "... var X millisekund", + "sendmail-rate-help": "Instruerar NodeBBs eposthanterare att begränsa antalet meddelanden som skickas samtidigt, för att inte översvämma mottagande eposttjänster. Dessa val gäller inte om SMTP Transport är aktiverat (nedan).", "smtp-transport": "SMTP Transport", "smtp-transport.enabled": "Use an external email server to send emails", @@ -33,8 +33,8 @@ "testing.select": "Select Email Template", "testing.send": "Send Test Email", "testing.send-help": "The test email will be sent to the currently logged in user's email address.", - "subscriptions": "Email Digests", - "subscriptions.disable": "Disable email digests", + "subscriptions": "Epostsammandrag", + "subscriptions.disable": "Avaktivera epostsammandrag", "subscriptions.hour": "Digest Hour", "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent " } \ No newline at end of file diff --git a/public/language/sv/admin/settings/general.json b/public/language/sv/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/sv/admin/settings/general.json +++ b/public/language/sv/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/sv/admin/settings/group.json b/public/language/sv/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/sv/admin/settings/group.json +++ b/public/language/sv/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/sv/admin/settings/user.json b/public/language/sv/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/sv/admin/settings/user.json +++ b/public/language/sv/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/sv/category.json b/public/language/sv/category.json index c44a23bdc4..c237e52bc9 100644 --- a/public/language/sv/category.json +++ b/public/language/sv/category.json @@ -10,13 +10,13 @@ "watch": "Bevaka", "ignore": "Ignorera", "watching": "Bevakar", - "not-watching": "Not Watching", + "not-watching": "Följer inte", "ignoring": "Ignorerar", - "watching.description": "Show topics in unread and recent", - "not-watching.description": "Do not show topics in unread, show in recent", - "ignoring.description": "Do not show topics in unread and recent", - "watching.message": "You are now watching updates from this category and all subcategories", - "notwatching.message": "You are not watching updates from this category and all subcategories", - "ignoring.message": "You are now ignoring updates from this category and all subcategories", + "watching.description": "Visa ämnen i olästa och senaste", + "not-watching.description": "Visa inte ämnen i olästa, visa i senaste", + "ignoring.description": "Visa inte ämnen i olästa och senaste", + "watching.message": "Nu får du uppdateringar från den här kategorin och alla underkategorier", + "notwatching.message": "Du får inga uppdateringar från den här kategorin eller alla underkategorier", + "ignoring.message": "Nu ignorerar du alla uppdateringar från den här kategorin och alla underkategorier.", "watched-categories": "Bevakade kategorier" } \ No newline at end of file diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 333ab54cda..c3522d4279 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -11,9 +11,9 @@ "invalid-uid": "Ogiltigt id för användare", "invalid-username": "Ogiltigt användarnamn", "invalid-email": "Ogiltig epostadress", - "invalid-fullname": "Invalid Fullname", - "invalid-location": "Invalid Location", - "invalid-birthday": "Invalid Birthday", + "invalid-fullname": "Ogiltigt namn", + "invalid-location": "Ogiltig plats", + "invalid-birthday": "Ogiltig födelsedag", "invalid-title": "Ogiltig titel", "invalid-user-data": "Ogiltig användardata", "invalid-password": "Ogiltigt lösenord", @@ -26,7 +26,7 @@ "invalid-pagination-value": "Ogiltigt värde för siduppdelning. Värdet måste vara mellan %1 och %2", "username-taken": "Användarnamn upptaget", "email-taken": "Epostadress upptagen", - "email-not-confirmed": "You are unable to post until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed": "Du kan inte posta förrän din epost är bekräftad. Vänligen klicka här för att bekräfta din epost.", "email-not-confirmed-chat": "Du kan ej använda chatten förrän din epostadress har blivit bekräftad, var god klicka här för att bekräfta din epostadress.", "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email. You won't be able to post or chat until your email is confirmed.", "no-email-to-confirm": "Detta forum kräver bekräftning av epostadresser, var god klicka här för att fylla i en epostadress", diff --git a/public/language/sv/flags.json b/public/language/sv/flags.json index 399752a53e..3c82a155d4 100644 --- a/public/language/sv/flags.json +++ b/public/language/sv/flags.json @@ -9,7 +9,7 @@ "updated": "Uppdatering", "target-purged": "Innehållet denna flagga refererar till har rensats bort och är inte längre tillgängligt.", - "graph-label": "Daily Flags", + "graph-label": "Dagliga flaggningar", "quick-filters": "Snabbfilter", "filter-active": "Ett eller flera filter är aktiva i denna lista med flaggor", "filter-reset": "Ta bort filter", diff --git a/public/language/sv/global.json b/public/language/sv/global.json index 0894cf0c86..c77c683845 100644 --- a/public/language/sv/global.json +++ b/public/language/sv/global.json @@ -59,8 +59,8 @@ "downvoted": "Nedröstad", "views": "Visningar", "reputation": "Rykte", - "lastpost": "Last post", - "firstpost": "First post", + "lastpost": "Senaste inlägget", + "firstpost": "Först inlägget", "read_more": "läs mer", "more": "Mer", "posted_ago_by_guest": "inskickad %1 av anonym", @@ -87,7 +87,7 @@ "language": "Språk", "guest": "Anonym", "guests": "Anonyma", - "former_user": "A Former User", + "former_user": "En före detta användare", "updated.title": "Forumet uppdaterades", "updated.message": "Det här forumet har nu uppdaterats till senaste versionen. Klicka här för att ladda om sidan.", "privacy": "Integritet", diff --git a/public/language/sv/groups.json b/public/language/sv/groups.json index 3e8df3981d..ba34743a8e 100644 --- a/public/language/sv/groups.json +++ b/public/language/sv/groups.json @@ -25,7 +25,7 @@ "details.latest_posts": "Senaste inlägg", "details.private": "Privat", "details.disableJoinRequests": "Inaktivera förfrågningar om att gå med", - "details.disableLeave": "Disallow users from leaving the group", + "details.disableLeave": "Tillåt inte att användare lämnar gruppen", "details.grant": "Tilldela/Dra tillbaka ägarskap", "details.kick": "Sparka ut", "details.kick_confirm": "Vill du verkligen avlägsna denna användare från gruppen?", @@ -37,8 +37,8 @@ "details.description": "Beskrivning", "details.badge_preview": "Förhandsgranskning av märke", "details.change_icon": "Byt ikon", - "details.change_label_colour": "Change Label Colour", - "details.change_text_colour": "Change Text Colour", + "details.change_label_colour": "Ändra etikettfärg", + "details.change_text_colour": "Ändra textfärg", "details.badge_text": "Märkestext", "details.userTitleEnabled": "Visa märke", "details.private_help": "Om aktiverat kommer en gruppägare behöva godkänna nya gruppmedlemmar", @@ -49,11 +49,11 @@ "event.updated": "Gruppinformationen har uppdaterats", "event.deleted": "Gruppen \"%1\" har tagits bort", "membership.accept-invitation": "Acceptera inbjudan", - "membership.accept.notification_title": "You are now a member of %1", + "membership.accept.notification_title": "Du är nu medlem i %1", "membership.invitation-pending": "Inbjudan väntar på svar", "membership.join-group": "Gå med i grupp", "membership.leave-group": "Lämna grupp", - "membership.leave.notification_title": "%1 has left group %2", + "membership.leave.notification_title": "%1 har lämnat gruppen %2", "membership.reject": "Neka", "new-group.group_name": "Gruppnamn:", "upload-group-cover": "Ladda upp omslagsbild för grupp", diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json index f48e1a9cf2..f65a606f3d 100644 --- a/public/language/sv/modules.json +++ b/public/language/sv/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Senaste chattarna", "chat.contacts": "Kontakter ", "chat.message-history": "Historik för meddelande", + "chat.message-deleted": "Message Deleted", "chat.options": "Chattinställningar", "chat.pop-out": "Utskjutande chatt", "chat.minimize": "Minimera", @@ -34,9 +35,9 @@ "chat.kick": "Sparka ut", "chat.show-ip": "Visa IP", "chat.owner": "Rummets ägare", - "chat.system.user-join": "%1 has joined the room", - "chat.system.user-leave": "%1 has left the room", - "chat.system.room-rename": "%2 has renamed this room: %1", + "chat.system.user-join": "%1 har anslutit till rummet", + "chat.system.user-leave": "%1 har lämnat rummet", + "chat.system.room-rename": "%2 har döpt om rummet: %1", "composer.compose": "Komponera", "composer.show_preview": "Visa förhandsgranskning", "composer.hide_preview": "Dölj förhandsgranskning", @@ -50,7 +51,7 @@ "composer.formatting.italic": "Kursiv", "composer.formatting.list": "Lista", "composer.formatting.strikethrough": "Genomstrykning", - "composer.formatting.code": "Code", + "composer.formatting.code": "Kod", "composer.formatting.link": "Länk", "composer.formatting.picture": "Bild", "composer.upload-picture": "Ladda upp bild", diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json index 7f9c3cbe74..8b1b549ad8 100644 --- a/public/language/sv/notifications.json +++ b/public/language/sv/notifications.json @@ -8,7 +8,7 @@ "outgoing_link_message": "Du lämnar nu %1", "continue_to": "Fortsätt till %1", "return_to": "Återgå till %1", - "new_notification": "You have a new notification", + "new_notification": "Du har en ny notis", "you_have_unread_notifications": "Du har olästa notiser.", "all": "Alla", "topics": "Ämnen", @@ -56,7 +56,7 @@ "notificationType_follow": "När någon börjar följa dig", "notificationType_new-chat": "När du får ett chattmeddelande", "notificationType_group-invite": "När du får en gruppinbjudan", - "notificationType_group-request-membership": "When someone requests to join a group you own", + "notificationType_group-request-membership": "När någon ber om att få gå med i en grupp du äger", "notificationType_new-register": "När någon läggs till i registreringskön", "notificationType_post-queue": "När ett nytt inlägg läggs i kön", "notificationType_new-post-flag": "När ett nytt inlägg flaggas", diff --git a/public/language/sv/pages.json b/public/language/sv/pages.json index de4be48be2..6927c93b47 100644 --- a/public/language/sv/pages.json +++ b/public/language/sv/pages.json @@ -43,10 +43,10 @@ "account/following": "Användare som %1 följer", "account/followers": "Användare som följer %1", "account/posts": "Inlägg skapade av %1", - "account/latest-posts": "Latest posts made by %1", + "account/latest-posts": "Senaste inläggen av %1", "account/topics": "Ämnen skapade av %1 ", "account/groups": "%1's grupper", - "account/watched_categories": "%1's Watched Categories", + "account/watched_categories": "%1's följda kategorier", "account/bookmarks": "%1'st bokmärkta inlägg", "account/settings": "Avnändarinställningar", "account/watched": "Ämnen som bevakas av %1", @@ -56,7 +56,7 @@ "account/best": "Bästa inläggen skapade av %1", "account/blocks": "Blockerade användare för %1", "account/uploads": "Uppladdningar av %1", - "account/sessions": "Login Sessions", + "account/sessions": "Inloggningssessioner", "confirm": "E-postadress bekräftad", "maintenance.text": "%1 genomgår underhåll just nu. Vänligen kom tillbaka lite senare.", "maintenance.messageIntro": "Utöver det så lämnade administratören följande meddelande:", diff --git a/public/language/sv/reset_password.json b/public/language/sv/reset_password.json index 4ec672d432..d25e7e5f9a 100644 --- a/public/language/sv/reset_password.json +++ b/public/language/sv/reset_password.json @@ -9,7 +9,7 @@ "repeat_password": "Bekräfta lösenord", "enter_email": "Var god fyll i din e-postadress så skickas ett e-postmeddelande med instruktioner hur du återställer ditt konto.", "enter_email_address": "Skriv in e-postadress", - "password_reset_sent": "If the specified address corresponds to an existing user account, a password reset email was sent. Please note that only one email will be sent per minute.", + "password_reset_sent": "Om den angedda adressen motsvarar en ett existerande användarkonto så skickas en epost med en lösenordsåterställning. Vänligen notera att endast en epost per minut kan skickas.", "invalid_email": "Felaktig e-post / E-post finns inte!", "password_too_short": "Lösenordet är för kort, var god välj ett annat lösenord.", "passwords_do_not_match": "De två lösenorden du har fyllt i matchar ej varandra.", diff --git a/public/language/sv/search.json b/public/language/sv/search.json index 60350b7d31..a920ce599b 100644 --- a/public/language/sv/search.json +++ b/public/language/sv/search.json @@ -17,7 +17,7 @@ "at-most": "Som mest", "relevance": "Relevans", "post-time": "Inläggstid", - "votes": "Votes", + "votes": "Röster", "newer-than": "Yngre än", "older-than": "Äldre än", "any-date": "Alla datum", @@ -31,7 +31,7 @@ "sort-by": "Sortera på", "last-reply-time": "Senaste svarstiden", "topic-title": "Ämnestitel", - "topic-votes": "Topic votes", + "topic-votes": "Ämnesröster", "number-of-replies": "Antal svar", "number-of-views": "Antal visningar", "topic-start-date": "Startdatum för ämne", @@ -44,5 +44,5 @@ "search-preferences-saved": "Sökinställningar sparade", "search-preferences-cleared": "Sökinställningar rensade", "show-results-as": "Visa resultat som", - "see-more-results": "See more results (%1)" + "see-more-results": "Se fler resultat (%1)" } \ No newline at end of file diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json index 4c8c21d7a0..664d951970 100644 --- a/public/language/sv/topic.json +++ b/public/language/sv/topic.json @@ -18,13 +18,13 @@ "last_reply_time": "Senaste svaret", "reply-as-topic": "Svara som ämne", "guest-login-reply": "Logga in för att posta", - "login-to-view": "🔒 Log in to view", + "login-to-view": "🔒 Logga in för att visa", "edit": "Ändra", "delete": "Ta bort", "purge": "Rensa", "restore": "Återställ", "move": "Flytta", - "change-owner": "Change Owner", + "change-owner": "Ändra ägare", "fork": "Grena", "link": "Länk", "share": "Dela", @@ -66,7 +66,7 @@ "thread_tools.move": "Flytta ämne", "thread_tools.move-posts": "Flytta inlägg", "thread_tools.move_all": "Flytta alla", - "thread_tools.change_owner": "Change Owner", + "thread_tools.change_owner": "Ändra ägare", "thread_tools.select_category": "Välj kategori", "thread_tools.fork": "Grena ämne", "thread_tools.delete": "Ta bort ämne", @@ -101,9 +101,9 @@ "delete_posts_instruction": "Klicka på inläggen du vill radera/rensa bort", "merge_topics_instruction": "Klicka på de ämnen du vill slå samman", "move_posts_instruction": "Klicka på de inlägg du vill flytta", - "change_owner_instruction": "Click the posts you want to assign to another user", + "change_owner_instruction": "Klicka på de inlägg du vill tilldela en annan användare", "composer.title_placeholder": "Skriv in ämnets titel här...", - "composer.handle_placeholder": "Namn", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Avbryt", "composer.submit": "Skicka", "composer.replying_to": "Svarar till %1", @@ -134,6 +134,6 @@ "diffs.no-revisions-description": "Detta inlägg har %1 revisioner.", "diffs.current-revision": "Nuvarande revision", "diffs.original-revision": "Ursprunglig revision", - "timeago_later": "%1 later", - "timeago_earlier": "%1 earlier" + "timeago_later": "%1 senare", + "timeago_earlier": "%1 tidigare" } \ No newline at end of file diff --git a/public/language/sv/user.json b/public/language/sv/user.json index 53ae179b22..fe3974fd85 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -25,11 +25,11 @@ "profile_views": "Profil-visningar", "reputation": "Rykte", "bookmarks": "Bokmärken", - "watched_categories": "Watched categories", - "change_all": "Change All", + "watched_categories": "Bevakade kategorier", + "change_all": "Ändra alla", "watched": "Bevakad", "ignored": "Ignorerad", - "default-category-watch-state": "Default category watch state", + "default-category-watch-state": "Förvalt bevakningsläge för kategori", "followers": "Följare", "following": "Följer", "blocks": "Blockerar", @@ -50,7 +50,7 @@ "change_picture": "Ändra bild", "change_username": "Ändra användarnamn", "change_email": "Ändra e-postadress", - "email_same_as_password": "Please enter your current password to continue – you've entered your new email again", + "email_same_as_password": "Vänligen skriv ditt lösenord flr att fortsätta – du har angett din nya epost igen", "edit": "Ändra", "edit-profile": "Redigera profil", "default_picture": "Standard-ikon", @@ -125,7 +125,7 @@ "follow_topics_you_reply_to": "Bevaka ämnen som du svarat på", "follow_topics_you_create": "Bevaka ämnen som du skapat", "grouptitle": "Grupptitel", - "group-order-help": "Select a group and use the arrows to order titles", + "group-order-help": "Välj en grupp och använd piltangenterna för att ordna rubriker", "no-group-title": "Ingen titel på gruppen", "select-skin": "Välj ett Skin", "select-homepage": "Välj en startsida", @@ -152,7 +152,7 @@ "info.moderation-note": "Moderations anteckning", "info.moderation-note.success": "Moderations anteckning sparad", "info.moderation-note.add": "Lägg till anteckning", - "sessions.description": "This page allows you to view any active sessions on this forum and revoke them if necessary. You can revoke your own session by logging out of your account.", + "sessions.description": "Denna sida låter dig se det här forumets alla aktiva sessioner och återkalla dem om den behövs. Du kan återkalla din egen session genom att logga ut från ditt eget konto.", "consent.title": "Dina rättigheter och Medgivande", "consent.lead": "Detta forum samlar och behandlar din personliga information.", "consent.intro": "Vi använder denna information endast i syfte att personligt anpassa din upplevelse i denna gemenskap, och för att associera de inlägg du gör till ditt användarkonto. När du registrerade dig blev du ombedd att ange ett användarnamn och en e-postadress, du kan även frivilligt ange ytterligare information för att komplettera din användarprofil på denna webbsida.

Vi sparar denna information så länge som du har ett konto hos oss, och du kan dra tillbaks ditt medgivande när som helst genom att radera ditt konto. Du kan är som helst efterfråga en kopia av din information på denna webbplats, genom sidan Rättigheter och Medgivande.

Om du har några frågor eller funderingar rekommenderar vi att du tar kontakt med detta forums administrativa team.", diff --git a/public/language/th/admin/development/info.json b/public/language/th/admin/development/info.json index f7572bb044..eea9193192 100644 --- a/public/language/th/admin/development/info.json +++ b/public/language/th/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "ข้อมูล - คุณกำลังอยู่บน %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes ตอบสนองแล้วภายใน %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/th/admin/manage/tags.json b/public/language/th/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/th/admin/manage/tags.json +++ b/public/language/th/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/th/admin/settings/advanced.json b/public/language/th/admin/settings/advanced.json index 4bd6b2aa60..5594bcd45d 100644 --- a/public/language/th/admin/settings/advanced.json +++ b/public/language/th/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/th/admin/settings/general.json b/public/language/th/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/th/admin/settings/general.json +++ b/public/language/th/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/th/admin/settings/group.json b/public/language/th/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/th/admin/settings/group.json +++ b/public/language/th/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/th/admin/settings/user.json b/public/language/th/admin/settings/user.json index eba3973569..9933184b3b 100644 --- a/public/language/th/admin/settings/user.json +++ b/public/language/th/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/th/modules.json b/public/language/th/modules.json index a96c51c16e..4c6e28fbc4 100644 --- a/public/language/th/modules.json +++ b/public/language/th/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "แชทล่าสุด", "chat.contacts": "ติดต่อ", "chat.message-history": "ประวัติข้อความ", + "chat.message-deleted": "Message Deleted", "chat.options": "ตัวเลือกแชท", "chat.pop-out": "Pop out แชท", "chat.minimize": "ย่อเล็กสุด", diff --git a/public/language/th/topic.json b/public/language/th/topic.json index 9783f4e67b..929be7d744 100644 --- a/public/language/th/topic.json +++ b/public/language/th/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Click the posts you want to move", "change_owner_instruction": "Click the posts you want to assign to another user", "composer.title_placeholder": "ป้อนชื่อกระทู้ของคุณที่นี่ ...", - "composer.handle_placeholder": "ชื่อ", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "ยกเลิก", "composer.submit": "ส่ง", "composer.replying_to": "ตอบไปยัง %1", diff --git a/public/language/tr/admin/development/info.json b/public/language/tr/admin/development/info.json index 82e57e01df..9f1e680abb 100644 --- a/public/language/tr/admin/development/info.json +++ b/public/language/tr/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Bilgi - Buradasınız: %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%2ms içinde %1 düğüm yanıt verdi!", "host": "sunucu", "pid": "pid", diff --git a/public/language/tr/admin/general/dashboard.json b/public/language/tr/admin/general/dashboard.json index 0a64746d2d..3b32449923 100644 --- a/public/language/tr/admin/general/dashboard.json +++ b/public/language/tr/admin/general/dashboard.json @@ -14,12 +14,12 @@ "page-views-custom-help": "Görüntülemek istediğiniz sayfa için görüntüleme tarih aralığı girin. Hiçbir tarih seçici yoksa, kabul edilebilir format YYYY-AA-GG'dir", "page-views-custom-error": "Lütfen tarih aralığını geçerli formatta girin YYYY-MM-DD", - "stats.yesterday": "Yesterday", - "stats.today": "Today", - "stats.last-week": "Last Week", - "stats.this-week": "This Week", - "stats.last-month": "Last Month", - "stats.this-month": "This Month", + "stats.yesterday": "Dün", + "stats.today": "Bugün", + "stats.last-week": "Geçen Hafta", + "stats.this-week": "Bu Hafta", + "stats.last-month": "Geçen Ay", + "stats.this-month": "Bu Ay", "stats.all": "Tüm Zamanlar", "updates": "Güncellemeler", diff --git a/public/language/tr/admin/manage/categories.json b/public/language/tr/admin/manage/categories.json index f6713cff74..48582d50b2 100644 --- a/public/language/tr/admin/manage/categories.json +++ b/public/language/tr/admin/manage/categories.json @@ -36,7 +36,7 @@ "privileges.section-viewing": "Ayrıcalıkları Görüntüle", "privileges.section-posting": "Gönderme Ayrıcalıkları", "privileges.section-moderation": "Moderatörlük Ayrıcalıkları", - "privileges.section-other": "Other", + "privileges.section-other": "Diğer", "privileges.section-user": "Kullanıcı", "privileges.search-user": "Kullanıcı Ekle", "privileges.no-users": "Bu kategoride kullanıcıya-özel ayrıcalıklar yok.", diff --git a/public/language/tr/admin/manage/digest.json b/public/language/tr/admin/manage/digest.json index 075b8e1aff..a48c0d66bb 100644 --- a/public/language/tr/admin/manage/digest.json +++ b/public/language/tr/admin/manage/digest.json @@ -1,9 +1,9 @@ { - "lead": "A listing of digest delivery stats and times is displayed below.", + "lead": "Özet teslim istatistiklerinin ve saatlerinin bir listesi aşağıda görüntülenmektedir.", "disclaimer": "Please be advised that email delivery is not guaranteed, due to the nature of email technology. Many variables factor into whether an email sent to the recipient server is ultimately delivered into the user's inbox, including server reputation, blacklisted IP addresses, and whether DKIM/SPF/DMARC is configured.", "disclaimer-continued": "A successful delivery means the message was sent successfully by NodeBB and acknowledged by the recipient server. It does not mean the email landed in the inbox. For best results, we recommend using a third-party email delivery service such as SendGrid.", - "user": "User", + "user": "Kullanıcı", "subscription": "Subscription Type", "last-delivery": "Last successful delivery", "default": "System default", diff --git a/public/language/tr/admin/manage/groups.json b/public/language/tr/admin/manage/groups.json index de64849055..293d63628a 100644 --- a/public/language/tr/admin/manage/groups.json +++ b/public/language/tr/admin/manage/groups.json @@ -1,12 +1,12 @@ { "name": "Grup Adı", - "badge": "Badge", - "properties": "Properties", + "badge": "Rozet", + "properties": "Özellikler", "description": "Grup Açıklaması", "member-count": "Üye Sayısı", - "system": "System", - "hidden": "Hidden", - "private": "Private", + "system": "Sistem", + "hidden": "Gizlenmiş", + "private": "Özel", "edit": "Düzenle", "search-placeholder": "Ara", "create": "Grup Oluştur", @@ -21,7 +21,7 @@ "edit.user-title": "Kullanıcıların Başlığı", "edit.icon": "Grup Simgesi", "edit.label-color": "Grubun Etiket Rengi", - "edit.text-color": "Group Text Color", + "edit.text-color": "Grup Yazı Rengi", "edit.show-badge": "Rozeti Göster", "edit.private-details": "Gruba katılmak için, eğer etkinse grup sahibinin onayı gerekir.", "edit.private-override": "Uyarı: Sistem düzeyinde özel gruplar devre dışı bırakıldı, bu seçenek onu geçersiz kılar.", diff --git a/public/language/tr/admin/manage/privileges.json b/public/language/tr/admin/manage/privileges.json index f6ce7f85fc..6b62d755c1 100644 --- a/public/language/tr/admin/manage/privileges.json +++ b/public/language/tr/admin/manage/privileges.json @@ -1,8 +1,8 @@ { "global": "Genel", "global.no-users": "Kullanıcıya özel genel ayrıcalık yok.", - "group-privileges": "Group Privileges", - "user-privileges": "User Privileges", + "group-privileges": "Grup Ayrıcalıkları", + "user-privileges": "Kullanıcı Ayrıcalıkları", "chat": "Sohbet", "upload-images": "Resim Yükle", "upload-files": "Dosya Yükle", @@ -11,11 +11,11 @@ "search-content": "İçerik Arama", "search-users": "Kullanıcıları Ara", "search-tags": "Etiketleri Ara", - "view-users": "View Users", - "view-tags": "View Tags", - "view-groups": "View Groups", + "view-users": "Kullanıcıları Görüntüle", + "view-tags": "Etiketleri Görüntüle", + "view-groups": "Grupları Görüntüle", "allow-local-login": "Yerel Giriş", - "allow-group-creation": "Group Create", + "allow-group-creation": "Grup Oluştur", "view-users-info": "View Users Info", "find-category": "Kategori Bul", "access-category": "Kategoriye Eriş", diff --git a/public/language/tr/admin/manage/tags.json b/public/language/tr/admin/manage/tags.json index a01a7535d1..2dc989f081 100644 --- a/public/language/tr/admin/manage/tags.json +++ b/public/language/tr/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Etiket yönetim sayfasını ziyaret etmek için buraya tıklayın.", "name": "Etiket Adı", - "alerts.editing-multiple": "Birden çok etiketi düzenle", - "alerts.editing-x": "\"% 1\" etiketi düzenleniyor", + "alerts.editing": "Etiket(ler)i Düzenle", "alerts.confirm-delete": "Seçilen etiketleri gerçekten silmek istiyor musunuz?", "alerts.update-success": "Etiket Güncellendi!" } \ No newline at end of file diff --git a/public/language/tr/admin/settings/advanced.json b/public/language/tr/admin/settings/advanced.json index abcc754175..d49b7d141e 100644 --- a/public/language/tr/admin/settings/advanced.json +++ b/public/language/tr/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Erişim-Kontrolü-Başlık-İzni", "hsts": "STS", "hsts.enabled": "HSTS'yi etkinleştir (önerilir)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Alt alanları HSTS üstbilgisine ekle", "hsts.preload": "HSTS üst bilgisinin ön yüklemesine izin ver", "hsts.help": "Etkinleştirildiğinde, bu site için bir HSTS başlığı ayarlanır. Alt alanları ve önyükleme bayraklarını dahil etmeyi seçebilirsiniz. Kararsızsanız, bu alanı işaretlenmemiş olarak bırakabilirsiniz. Daha fazla bilgi ", diff --git a/public/language/tr/admin/settings/email.json b/public/language/tr/admin/settings/email.json index 818afc9a23..b460b44687 100644 --- a/public/language/tr/admin/settings/email.json +++ b/public/language/tr/admin/settings/email.json @@ -33,8 +33,8 @@ "testing.select": "E-posta Kalıbını Seç", "testing.send": "Test E-postası Gönder", "testing.send-help": "The test email will be sent to the currently logged in user's email address.", - "subscriptions": "Email Digests", - "subscriptions.disable": "Disable email digests", + "subscriptions": "Özet E-postaları", + "subscriptions.disable": "Özet e-postalarını kapat", "subscriptions.hour": "Digest Hour", "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent " } \ No newline at end of file diff --git a/public/language/tr/admin/settings/general.json b/public/language/tr/admin/settings/general.json index f3848ce044..b6bc1e0077 100644 --- a/public/language/tr/admin/settings/general.json +++ b/public/language/tr/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Ayarları", "title": "Site Başlığı", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "Bağlantı", "title.url-placeholder": "Site başlığının URL adresi", "title.url-help": "Başlık tıklandığında, kullanıcıları bu adrese gönder. Boş bırakılırsa, kullanıcı forum dizinine gönderilir.", @@ -31,5 +33,9 @@ "outgoing-links": "Harici Bağlantılar", "outgoing-links.warning-page": "Dışarı giden bağlantılar için uyarı sayfası kullan", "search-default-sort-by": "Aramada varsayılan sıralama", - "outgoing-links.whitelist": "Uyarı sayfasını atlamak için beyaz listeye eklenecek alan-adları" -} \ No newline at end of file + "outgoing-links.whitelist": "Uyarı sayfasını atlamak için beyaz listeye eklenecek alan-adları", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/tr/admin/settings/group.json b/public/language/tr/admin/settings/group.json index 9c6ac12028..5d8ecd164b 100644 --- a/public/language/tr/admin/settings/group.json +++ b/public/language/tr/admin/settings/group.json @@ -3,9 +3,10 @@ "private-groups": "Gizli Grup", "private-groups.help": "Eğer etkinse, gruba katılmak için grup sahibinin onayı gerekir. (Varsayılan: Etkin)", "private-groups.warning": "Dikkat! Eğer bu opsiyon devre dışıysa ve özel gruplarınız varsa, onlar otomatik olarak genele dönüşecektir.", + "allow-multiple-badges": "Çoklu rozete izin ver", "allow-multiple-badges-help": "Bu bayrak, kullanıcıların birden fazla grup rozetini seçmelerine izin vermek için kullanılabilir, tema desteği gerektirir.", "max-name-length": "Maksimum Grup Adı Uzunluğu", - "max-title-length": "Maximum Group Title Length", + "max-title-length": "Grup İsmi Azami Uzunluğu", "cover-image": "Grup Kapak Resmi", "default-cover": "Varsayılan Kapak Resmi", "default-cover-help": "Kapak resmi olmayan grupları, varsayılan kapak resimlerini kullanmaları için virgülle ayırarak ekleyin" diff --git a/public/language/tr/admin/settings/post.json b/public/language/tr/admin/settings/post.json index 3813a19dcd..4557522d51 100644 --- a/public/language/tr/admin/settings/post.json +++ b/public/language/tr/admin/settings/post.json @@ -7,11 +7,11 @@ "sorting.most-posts": "En çok yazılanlar", "sorting.topic-default": "Varsayılan Konu Sıralaması", "length": "İleti Uzunluğu", - "post-queue": "Post Queue", + "post-queue": "İleti Kuyruğu", "restrictions": "İleti Kısıtlamaları", "restrictions-new": "Yeni Kullanıcı Kısıtlamaları", "restrictions.post-queue": "İleti kuyruğunu etkinleştir", - "restrictions.post-queue-rep-threshold": "Reputation required to bypass post queue", + "restrictions.post-queue-rep-threshold": "İleti kuyruğuna girmemek için gereken saygınlık sayısı", "restrictions.groups-exempt-from-post-queue": "Select groups that should be exempt from the post queue", "restrictions-new.post-queue": "Yeni kullanıcı kısıtlamalarını etkinleştir", "restrictions.post-queue-help": "Enabling post queue will put the posts of new users in a queue for approval", diff --git a/public/language/tr/admin/settings/user.json b/public/language/tr/admin/settings/user.json index d49594de72..f6d1e50c91 100644 --- a/public/language/tr/admin/settings/user.json +++ b/public/language/tr/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Sadece E-posta", "account-settings": "Hesap Ayarları", "gdpr_enabled": "GDPR veri toplamayı etkinleştir", - "gdpr_enabled_help": "Etkinleştirildiğinde, tüm yeni tescil ettirenlerin Veri Toplama Yönetmeliği (GDPR) kapsamında veri toplanması ve kullanımı için açık bir şekilde onay vermeleri gerekecektir. Not: GDPR'yi etkinleştirmek, önceden var olan kullanıcıları onay vermesi için zorlamaz. Bunu yapmak için GDPR eklentisini yüklemeniz gerekir.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Kullanıcı adı değişikliği kapalı", "disable-email-changes": "E-posta değişikliklerini devre dışı bırak", "disable-password-changes": "Parola değişikliği kapalı", @@ -74,6 +74,6 @@ "default-notification-settings": "Varsayılan bildirim ayarları", "categoryWatchState": "Default category watch state", "categoryWatchState.watching": "Watching", - "categoryWatchState.notwatching": "Not Watching", + "categoryWatchState.notwatching": "Takip edilmiyor", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/tr/category.json b/public/language/tr/category.json index 3681fa37e6..8c73105ffb 100644 --- a/public/language/tr/category.json +++ b/public/language/tr/category.json @@ -2,21 +2,21 @@ "category": "Kategori", "subcategories": "Alt kategoriler", "new_topic_button": "Yeni Başlık", - "guest-login-post": "Göndermek için giriş yapın", + "guest-login-post": "İleti göndermek için giriş yapın", "no_topics": " Bu kategoride hiç konu yok.
Yeni bir konu açmak istemez misiniz?", "browsing": "gözden geçiriliyor", "no_replies": "Kimse yanıtlamadı", "no_new_posts": "Yeni ileti yok", - "watch": "İzle", - "ignore": "Yoksay", - "watching": "İzleniyor", - "not-watching": "Not Watching", - "ignoring": "Yoksayılıyor", - "watching.description": "Show topics in unread and recent", - "not-watching.description": "Do not show topics in unread, show in recent", - "ignoring.description": "Do not show topics in unread and recent", - "watching.message": "You are now watching updates from this category and all subcategories", - "notwatching.message": "You are not watching updates from this category and all subcategories", - "ignoring.message": "You are now ignoring updates from this category and all subcategories", + "watch": "Takip et", + "ignore": "Yok say", + "watching": "Takip ediliyor", + "not-watching": "Takip edilmiyor", + "ignoring": "Yok sayılıyor", + "watching.description": "Konuyu okunmamış ve güncel konular arasında göster", + "not-watching.description": "Konuyu okunmamış konular arasında gösterme ama güncel konular arasında göster", + "ignoring.description": "Konuyu okunmamış ve güncel konular arasında gösterme", + "watching.message": "Bu kategori ve alt kategorilerindeki güncellemeleri artık takip ediyorsunuz", + "notwatching.message": "Bu kategori ve alt kategorilerindeki güncellemeleri artık takip etmiyorsunuz", + "ignoring.message": "Bu kategori ve alt kategorilerindeki güncellemeleri artık yok sayıyorsunuz", "watched-categories": "Takip edilen kategoriler" } \ No newline at end of file diff --git a/public/language/tr/email.json b/public/language/tr/email.json index 3cd4d85aa5..ee981ad44a 100644 --- a/public/language/tr/email.json +++ b/public/language/tr/email.json @@ -27,9 +27,9 @@ "digest.week": "hafta", "digest.month": "ay", "digest.subject": "%1 için özet", - "digest.title.day": "Your Daily Digest", - "digest.title.week": "Your Weekly Digest", - "digest.title.month": "Your Monthly Digest", + "digest.title.day": "Günlük Özet", + "digest.title.week": "Haftalık Özet", + "digest.title.month": "Aylık Özet", "notif.chat.subject": "Okunmamış bazı iletileriniz var", "notif.chat.cta": "Sohbete devam etmek için buraya tıklayın", "notif.chat.unsub.info": "Bu bildirim şectiğiniz ayarlar yüzünden gönderildi.", @@ -37,7 +37,7 @@ "notif.post.unsub.one-click": "Alternatively, unsubscribe from future emails like this, by clicking", "notif.cta": "To the forum", "notif.cta-new-reply": "View Post", - "notif.cta-new-chat": "View Chat", + "notif.cta-new-chat": "Sohbeti Görüntüle", "notif.test.short": "Testing Notifications", "notif.test.long": "This is a test of the notifications email. Send help!", "test.text1": "Bu ileti NodeBB e-posta ayarlarınızın doğru çalışıp çalışmadığını kontrol etmek için gönderildi.", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index c8caa73ec8..262efe5776 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -2,7 +2,7 @@ "invalid-data": "Geçersiz Veri", "invalid-json": "Geçersiz JSON", "not-logged-in": "Giriş yapmamış görünüyorsunuz.", - "account-locked": "Hesabınız geçici olarak kitlendi", + "account-locked": "Hesabınız geçici olarak kilitlendi", "search-requires-login": "Arama hesap gerektiriyor. Lütfen giriş yapın ya da kaydolun.", "goback": "Bir önceki sayfaya dönmek için geri tuşuna basın", "invalid-cid": "Geçersiz Kategori ID", @@ -11,9 +11,9 @@ "invalid-uid": "Geçersiz Kullanıcı ID", "invalid-username": "Geçersiz Kullanıcı İsmi", "invalid-email": "Geçersiz E-posta", - "invalid-fullname": "Invalid Fullname", - "invalid-location": "Invalid Location", - "invalid-birthday": "Invalid Birthday", + "invalid-fullname": "Hatalı İsim", + "invalid-location": "Hatalı Konum", + "invalid-birthday": "Hatalı Doğumgünü", "invalid-title": "Geçersiz başlık", "invalid-user-data": "Geçersiz Kullancı Verisi", "invalid-password": "Geçersiz Şifre", @@ -26,9 +26,9 @@ "invalid-pagination-value": "Geçersiz sayfalama değeri, en az %1 ve en fazla %2 olabilir", "username-taken": "Kullanıcı İsmi Alınmış", "email-taken": "E-posta Alınmış", - "email-not-confirmed": "You are unable to post until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed": "E-posta adresiniz onaylanmadan ileti gönderemezsiniz, onay e-postası için buraya tıklayınız!", "email-not-confirmed-chat": "E-postanız onaylanana kadar sohbet edemezsiniz, onaylamak için lütfen buraya tıklayın.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email. You won't be able to post or chat until your email is confirmed.", + "email-not-confirmed-email-sent": "E-posta adresiniz henüz onaylanmamış, lütfen onay e-postası için gelen kutunuzu kontrol ediniz. E-posta adresinizi onaylayana kadar foruma ileti gönderemeyeceksiniz veya sohbet edemeyeceksiniz!", "no-email-to-confirm": "Bu forum e-posta doğrulaması gerektirir, lütfen buraya bir e-posta adresi girin", "email-confirm-failed": "E-posta adresinizi doğrulayamıyoruz. Lütfen daha sonra tekrar deneyin.", "confirm-email-already-sent": "E-posta onayı zaten gönderilmiş, yeni bir onay göndermek için lütfen %1 dakika bekleyin.", @@ -103,8 +103,8 @@ "group-needs-owner": "Bu grubu en az bir kişi sahiplenmesi gerekiyor", "group-already-invited": "Bu kullanıcı zaten davet edilmiş", "group-already-requested": "Üyelik isteğiniz zaten gönderildi", - "group-join-disabled": "You are not able to join this group at this time", - "group-leave-disabled": "You are not able to leave this group at this time", + "group-join-disabled": "Gruba şu anda katılamazsınız!", + "group-leave-disabled": "Grubu şu anda terk edemezsiniz! ", "post-already-deleted": "İleti zaten silinmiş", "post-already-restored": "İleti zaten geri getirilmiş", "topic-already-deleted": "Başlık zaten silinmiş", diff --git a/public/language/tr/flags.json b/public/language/tr/flags.json index 31679b4f71..f377db9f95 100644 --- a/public/language/tr/flags.json +++ b/public/language/tr/flags.json @@ -9,7 +9,7 @@ "updated": "Güncellendi", "target-purged": "İlgili bayrağın içeriği temizlendi ve artık mevcut değil.", - "graph-label": "Daily Flags", + "graph-label": "Günlük Bayraklar", "quick-filters": "Akıllı Filtre", "filter-active": "Bayraklar listesinde etkin olan bir veya daha fazla filtre var", "filter-reset": "Filtreleri Kaldır", diff --git a/public/language/tr/global.json b/public/language/tr/global.json index ad2bcd28ac..7d3c90d0b8 100644 --- a/public/language/tr/global.json +++ b/public/language/tr/global.json @@ -18,7 +18,7 @@ "posting_restriction_info": "İleti gönderme sadece kayıtlı kullancılar içindir, giriş yapmak için buraya tıklayın.", "welcome_back": "Tekrar Hoş Geldiniz", "you_have_successfully_logged_in": "Başarıyla giriş yaptınız!", - "save_changes": "Değişiklikleri kaydet", + "save_changes": "Değişiklikleri Kaydet", "save": "Kaydet", "close": "Kapat", "pagination": "Sayfalara numara koyma", @@ -59,15 +59,15 @@ "downvoted": "Eksi", "views": "Bakış", "reputation": "İtibar", - "lastpost": "Last post", - "firstpost": "First post", + "lastpost": "Son ileti", + "firstpost": "İlk ileti", "read_more": "daha fazla oku", "more": "Daha Fazla", "posted_ago_by_guest": "Ziyaretçi tarafından %1 yayımlandı", "posted_ago_by": "%2 tarafından %1 yayımlandı", "posted_ago": "%1 yayımlandı", - "posted_in": "%1 yayınlandı", - "posted_in_by": "%2 tarafından %1 yayınlandı", + "posted_in": "%1 içinde yayımlandı", + "posted_in_by": "%2 tarafından %1 içinde yayımlandı", "posted_in_ago": "%1 içinde %2 yayımlandı", "posted_in_ago_by": "%1 içinde %3 tarafından %2 yayımlandı", "user_posted_ago": "%1 %2 yayımladı", diff --git a/public/language/tr/groups.json b/public/language/tr/groups.json index 106164d283..f2aad40a0b 100644 --- a/public/language/tr/groups.json +++ b/public/language/tr/groups.json @@ -14,7 +14,7 @@ "invited.search": "Gruba davet etmek için kullanıcı ara", "invited.notification_title": "%1 grubuna katılmaya davet edildiniz", "request.notification_title": "%1 grup daveti gönderdi", - "request.notification_text": "%1 %2 grubuna katılmak istiyor", + "request.notification_text": "%1 , %2 grubuna katılmak istiyor", "cover-save": "Kaydet", "cover-saving": "Kaydediliyor", "details.title": "Grup Detayları", @@ -25,7 +25,7 @@ "details.latest_posts": "En son iletiler", "details.private": "Özel", "details.disableJoinRequests": "Katılma isteklerini devre dışı bırak", - "details.disableLeave": "Disallow users from leaving the group", + "details.disableLeave": "Üyelerin gruptan ayrılmasını yasakla", "details.grant": "Grup Sahibi Yap/Kaldır", "details.kick": "Dışarı at", "details.kick_confirm": "Bu üyeyi bu gruptan silmek istediğinden emin misin?", @@ -37,8 +37,8 @@ "details.description": "Tanımlama", "details.badge_preview": "Rozet Önizlemesi", "details.change_icon": "İkonu Değiştir", - "details.change_label_colour": "Change Label Colour", - "details.change_text_colour": "Change Text Colour", + "details.change_label_colour": "Etiket Rengini Değiştir", + "details.change_text_colour": "Yazı Rengini Değiştir", "details.badge_text": "Rozet Yazısı", "details.userTitleEnabled": "Rozeti Göster", "details.private_help": "Gruba katılmak için eğer etkinse grup sahibini onayı gerekir, ", @@ -49,7 +49,7 @@ "event.updated": "Grup detayları güncellenmiştir", "event.deleted": "\"%1\" grubu silinmiş", "membership.accept-invitation": "Daveti Kabul Et", - "membership.accept.notification_title": "You are now a member of %1", + "membership.accept.notification_title": " %1 grubunun üyesi oldunuz!", "membership.invitation-pending": "Davet beklemede", "membership.join-group": "Gruba Katıl", "membership.leave-group": "Gruptan Ayrıl", diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json index db7693e0e6..9866c1bd8f 100644 --- a/public/language/tr/modules.json +++ b/public/language/tr/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Güncel Sohbetler", "chat.contacts": "Kontaklar", "chat.message-history": "Mesaj Geçmişi", + "chat.message-deleted": "Mesaj Silindi", "chat.options": "Sohbet Ayarları", "chat.pop-out": "Sohbeti Pencereye Çevir", "chat.minimize": "Küçült", @@ -34,15 +35,15 @@ "chat.kick": "Dışarı At", "chat.show-ip": "IP Göster", "chat.owner": "Oda Sahibi", - "chat.system.user-join": "%1 has joined the room", - "chat.system.user-leave": "%1 has left the room", - "chat.system.room-rename": "%2 has renamed this room: %1", + "chat.system.user-join": "%1 odaya katıldı", + "chat.system.user-leave": "%1 odadan çıktı", + "chat.system.room-rename": "%2 şu grubun ismini değiştirdi: %1", "composer.compose": "Yaz", "composer.show_preview": "Önizleme Göster", "composer.hide_preview": "Önizleme Sakla", "composer.user_said_in": "%1, içinde söyledi: %2", "composer.user_said": "%1 söyledi:", - "composer.discard": "Bu iletiyi iptal etmek istediğinizden eminmisiniz?", + "composer.discard": "Bu iletiyi iptal etmek istediğinizden emin misiniz?", "composer.submit_and_lock": "Gönder ve Kitle", "composer.toggle_dropdown": "Menü aç", "composer.uploading": "Yükleniyor %1", @@ -57,7 +58,7 @@ "composer.upload-file": "Dosya Yükle", "composer.zen_mode": "Zen Modu", "composer.select_category": "Bir kategori seç", - "bootbox.ok": "Tamam", + "bootbox.ok": "Kabul", "bootbox.cancel": "İptal", "bootbox.confirm": "Onayla", "cover.dragging_title": "Kapak Görseli Konumlandırma", diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json index 826b51d401..264c08b86d 100644 --- a/public/language/tr/notifications.json +++ b/public/language/tr/notifications.json @@ -1,13 +1,13 @@ { "title": "Bildirimler", - "no_notifs": "Yeni bildirimleriniz yok", + "no_notifs": "Yeni bildiriminiz yok", "see_all": "Bütün bildirimleri gör", "mark_all_read": "Tüm bildirimleri okunmuş say", - "back_to_home": "Geri dön %1", + "back_to_home": "%1 'a geri dön", "outgoing_link": "Harici Link", - "outgoing_link_message": "%1 ayrılıyorsunuz", - "continue_to": "Devam et", - "return_to": "Geri dön.", + "outgoing_link_message": "%1 'dan ayrılıyorsunuz", + "continue_to": "%1 'a devam et", + "return_to": "%1 'a geri dön", "new_notification": "Yeni bir bildiriminiz var", "you_have_unread_notifications": "Okunmamış bildirimleriniz var.", "all": "Hepsi", @@ -20,27 +20,27 @@ "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.", - "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ı", - "moved_your_topic": "%1 taşındı %2", - "user_flagged_post_in": "%1 bir iletiyi bayrakladı. %2", - "user_flagged_post_in_dual": " %1 ve %2 %3 gönderini bayrakladı", - "user_flagged_post_in_multiple": "%1 ve %2 kişi daha %3 gönderini bayrakladı", - "user_flagged_user": "%1 bayraklanan bir kullanıcı profili (%2)", - "user_flagged_user_dual": "%1 ve %2 bayraklanan kullanıcı profili (%3)", - "user_flagged_user_multiple": "%1 ve %2 diğer bayraklanan kullanıcı profili (%3)", - "user_posted_to": "%1 %2 başlığına bir ileti gönderdi.", - "user_posted_to_dual": "%1 ve %2 gönderine cevap verdi: %3", - "user_posted_to_multiple": "%1 ve %2 kişi daha gönderine cevap verdi: %3", - "user_posted_topic": "%1 yeni bir konu yarattı: %2", + "upvoted_your_post_in": "%1 şu konudaki iletinizi beğendi: %2.", + "upvoted_your_post_in_dual": "%1 ve %2 şu konudaki iletinizi beğendi: %3", + "upvoted_your_post_in_multiple": "%1 ve %2 iki kişi daha şu konudaki iletinizi beğendi: %3 ", + "moved_your_post": "%1, iletinizi şuraya taşıdı: %2 ", + "moved_your_topic": "%1 şuraya taşındı: %2", + "user_flagged_post_in": "%1 şu konudaki bir iletiyi bayrakladı. %2", + "user_flagged_post_in_dual": " %1 ve %2 şu konudaki bir iletiyi bayrakladı: %3", + "user_flagged_post_in_multiple": "%1 ve %2 kişi daha şu konudaki iletiyi bayrakladı: %3", + "user_flagged_user": "%1şu kullanıcının profilini bayrakladı: (%2)", + "user_flagged_user_dual": "%1 ve %2 şu kullanıcının profilini bayrakladı: (%3)", + "user_flagged_user_multiple": "%1 ve %2 kişi daha şu kullanıcının profilini bayrakladı: (%3)", + "user_posted_to": "%1şu başlıktaki gönderinize cevap verdi: %2 ", + "user_posted_to_dual": "%1 ve %2 şu başlıktaki gönderinize cevap verdi: %3", + "user_posted_to_multiple": "%1 ve %2 kişi daha şu başlıktaki gönderinize cevap verdi: %3", + "user_posted_topic": "%1 şu yeni konuyu yarattı: %2", "user_started_following_you": "%1 sizi takip etmeye başladı.", - "user_started_following_you_dual": "%1 ve %2 seni takip etmeye başladı.\n", - "user_started_following_you_multiple": "%1 ve %2 kişi daha seni takip etmeye başladı.", + "user_started_following_you_dual": "%1 ve %2 sizi takip etmeye başladı.", + "user_started_following_you_multiple": "%1 ve %2 kişi daha sizi takip etmeye başladı.", "new_register": "%1 kayıt olma isteği gönderdi.", "new_register_multiple": "Beklemede %1 kayıt olma isteği bulunmaktadır.", - "flag_assigned_to_you": "Bayrak %1 size devredildi", + "flag_assigned_to_you": "Bayrak %1 size devredildi", "post_awaiting_review": "İleti inceleme bekliyor", "email-confirmed": "E-posta onaylandı", "email-confirmed-message": "E-postanızı onaylandığınız için teşekkürler. Hesabınız tamamen aktif edildi.", @@ -56,7 +56,7 @@ "notificationType_follow": "Birisi seni takip etmeye başlayınca", "notificationType_new-chat": "Bir sohbet mesajı aldığınızda", "notificationType_group-invite": "Bir grup davetiyesi aldığınızda", - "notificationType_group-request-membership": "When someone requests to join a group you own", + "notificationType_group-request-membership": "Birisi sana ait bir gruba üye olmak istediğinde", "notificationType_new-register": "Birisi kayıt kuyruğuna eklendiğinde", "notificationType_post-queue": "Yeni bir ileti sıraya alındığında", "notificationType_new-post-flag": "Bir ileti bayraklandığında", diff --git a/public/language/tr/pages.json b/public/language/tr/pages.json index a633636ed7..dd31df1cd1 100644 --- a/public/language/tr/pages.json +++ b/public/language/tr/pages.json @@ -43,7 +43,7 @@ "account/following": "%1 tarafından takip edilenler", "account/followers": "%1 takip edenler", "account/posts": "%1 tarafından gönderilen iletiler", - "account/latest-posts": "Latest posts made by %1", + "account/latest-posts": "%1 tarafından gönderilen son iletiler", "account/topics": "%1 tarafından gönderilen başlıklar", "account/groups": "%1 Kişisine Ait Gruplar", "account/watched_categories": "%1's Takip Edilen Kategori", diff --git a/public/language/tr/reset_password.json b/public/language/tr/reset_password.json index 179182d89e..6c1319dd92 100644 --- a/public/language/tr/reset_password.json +++ b/public/language/tr/reset_password.json @@ -9,7 +9,7 @@ "repeat_password": "Şifreyi Onayla", "enter_email": "Lütfen e-posta adresinizi girin , size hesabınızı nasıl sıfırlayacağınızı anlatan bir e-posta gönderelim", "enter_email_address": "E-posta Adresinizi Girin", - "password_reset_sent": "If the specified address corresponds to an existing user account, a password reset email was sent. Please note that only one email will be sent per minute.", + "password_reset_sent": "Girilen adres var olan bir kullanıcıya aitse, kendisine şifre yenileme e-postası gönderildi. Dakikada sadece bir e-posta gönderebileceğinizi unutmayın! ", "invalid_email": "Geçersiz E-posta / E-posta mevcut değil!", "password_too_short": "Girdiğiniz şifre çok kısa, lütfen farklı bir şifre seçiniz.", "passwords_do_not_match": "Girdiğiniz iki şifre birbirine uymuyor.", diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index 9669d68aef..0757fa4bff 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -18,25 +18,25 @@ "last_reply_time": "Son cevap", "reply-as-topic": "Konuyu cevapla", "guest-login-reply": "Cevaplamak için giriş yapın", - "login-to-view": "🔒 Log in to view", + "login-to-view": "Görüntülemek için giriş yap!", "edit": "Düzenle", "delete": "Sil", "purge": "Temizle", "restore": "Geri Getir", "move": "Taşı", - "change-owner": "Change Owner", + "change-owner": "Sahibini Değiştir", "fork": "Ayır", "link": "Bağlantı", "share": "Paylaş", "tools": "Araçlar", "locked": "Kilitli", - "pinned": "İğnelendi", + "pinned": "Sabitlendi", "moved": "Taşındı", "copy-ip": "IP Kopyala", "ban-ip": "IP Yasakla", "view-history": "Geçmişi Düzenle", "bookmark_instructions": "Bu konuda en son kaldığın yere dönmek için tıkla.", - "flag_title": "Bu iletiyi moderatöre haber et", + "flag_title": "Bu iletiyi moderatöre bildir", "merged_message": "Bu konu %2 olarak birleştirildi", "deleted_message": "Bu konu silindi. Sadece konu düzenleme yetkisi olan kullanıcılar görebilir.", "following_topic.message": "Artık bir kullanıcı bu konuya ileti gönderdiğinde bildirim alacaksınız.", @@ -53,10 +53,10 @@ "share_this_post": "Bu iletiyi paylaş", "watching": "Takip ediliyor", "not-watching": "Takip edilmiyor", - "ignoring": "Sustur", - "watching.description": "Yeni bir ileti geldiğinde beni bildir.
Okunmamış olarak göster.", - "not-watching.description": "Yeni bir ileti geldiğinde bildirme.
Kategori susturulmamışsa okunmamış olarak göster.", - "ignoring.description": "Yeni bir ileti geldiğinde bildirme.
Okunmamış olarak gösterme.", + "ignoring": "Susturulmuş", + "watching.description": "Yeni bir ileti geldiğinde bana bildir.
Konuyu okunmamış olarak göster.", + "not-watching.description": "Yeni bir ileti geldiğinde bana bildirme.
Kategori susturulmamışsa okunmamış olarak göster.", + "ignoring.description": "Yeni bir ileti geldiğinde bana bildirme.
Okunmamış olarak gösterme.", "thread_tools.title": "Konu Ayarları", "thread_tools.markAsUnreadForAll": "Okunmamış olarak İşaretle", "thread_tools.pin": "Konuyu Sabitle", @@ -66,7 +66,7 @@ "thread_tools.move": "Konuyu Taşı", "thread_tools.move-posts": "İletiyi Taşı", "thread_tools.move_all": "Hepsini Taşı", - "thread_tools.change_owner": "Change Owner", + "thread_tools.change_owner": "Sahibini Değiştir", "thread_tools.select_category": "Kategori Seç", "thread_tools.fork": "Konuyu Ayır", "thread_tools.delete": "Konuyu Sil", @@ -81,7 +81,7 @@ "topic_move_success": "Konu %1 kategorisine başarıyla taşındı.", "post_delete_confirm": "Bu iletiyi gerçekten silmek istediğinize emin misiniz?", "post_restore_confirm": "Bu iletiyi gerçekten geri getirmek istiyor musun?", - "post_purge_confirm": "Bu iletiyi temizlemek istediğinize eminmisiniz?", + "post_purge_confirm": "Bu iletiyi temizlemek istediğinize emin misiniz?", "load_categories": "Kategoriler Yükleniyor", "confirm_move": "Taşı", "confirm_fork": "Ayır", @@ -101,9 +101,9 @@ "delete_posts_instruction": "Silmek/temizlemek istediğiniz iletilere tıklayın.", "merge_topics_instruction": "Birleştirmek istediğiniz konulara tıklayın", "move_posts_instruction": "Taşımak istediğin iletilere tıklayın", - "change_owner_instruction": "Click the posts you want to assign to another user", + "change_owner_instruction": "Başka kullanıcıya aktarmak istediğiniz iletileri seçiniz", "composer.title_placeholder": "Konu ismini buraya girin...", - "composer.handle_placeholder": "İsim", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Vazgeç", "composer.submit": "Gönder", "composer.replying_to": "Yanıtlanan Konu %1", @@ -134,6 +134,6 @@ "diffs.no-revisions-description": "Bu iletinin %1 revizyonu var.", "diffs.current-revision": "mevcut revizyon", "diffs.original-revision": "orijinal revizyon", - "timeago_later": "%1 later", - "timeago_earlier": "%1 earlier" + "timeago_later": "%1 sonra", + "timeago_earlier": "%1 daha öncesi" } \ No newline at end of file diff --git a/public/language/tr/user.json b/public/language/tr/user.json index 99c4062eff..37a1378e25 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -26,13 +26,13 @@ "reputation": "Saygınlık", "bookmarks": "Yer İmleri", "watched_categories": "Takip edilen kategoriler", - "change_all": "Change All", + "change_all": "Hepsini Değiştir", "watched": "Takip edildi", "ignored": "Susturuldu", - "default-category-watch-state": "Default category watch state", + "default-category-watch-state": "Varsayılan kategori izleme durumu", "followers": "Takipçiler", "following": "Takip Ediyor", - "blocks": "Blok", + "blocks": "Engellenenler", "block_toggle": "Blokta Geçiş Yap", "block_user": "Kullanıcı Engelle", "unblock_user": "Kullanıcı Engeli Kaldır", @@ -42,7 +42,7 @@ "chat": "Sohbet", "chat_with": "%1 ile sohbete devam et", "new_chat_with": "%1 ile yeni sohbete başla", - "flag-profile": "Bayrak Profili", + "flag-profile": "Profili Bayrakla", "follow": "Takip Et", "unfollow": "Takip etme", "more": "Daha Fazla", @@ -91,7 +91,7 @@ "follows_no_one": "Bu kullanıcı kimseyi takip etmiyor :(", "has_no_posts": "Bu kullanıcı henüz herhangi bir ileti yazmamış.", "has_no_topics": "Bu kullanıcı henüz hiç bir konu açmamış.", - "has_no_watched_topics": "Bu kullanıcı henüz hiç bir konu okumamış.", + "has_no_watched_topics": "Bu kullanıcı henüz hiç bir konuyu takip etmiyor.", "has_no_ignored_topics": "Bu kullanıcı henüz hiçbir konuyu yok saymadı.", "has_no_upvoted_posts": "Bu kullanıcı henüz hiç bir gönderiyi artılamamış.", "has_no_downvoted_posts": "Bu kullanıcı henüz hiç bir gönderiyi eksilememiş.", @@ -125,15 +125,15 @@ "follow_topics_you_reply_to": "Cevap verdiğim konuları takip et", "follow_topics_you_create": "Oluşturduğum konuları takip et", "grouptitle": "Grup Başlığı", - "group-order-help": "Select a group and use the arrows to order titles", + "group-order-help": "Bir grup seçin ve başlıkları sıralamak için yön tuşlarını kullanın", "no-group-title": "Grup başlığı yok", "select-skin": "Bir tema seçin", - "select-homepage": "Bir anasayfa seçin", + "select-homepage": "Bir Anasayfa Seçin", "homepage": "Anasayfa", "homepage_description": "Anasayfa olarak kullanacağınız sayfayı seçin veya \"Hiçbiri\" diyerek varsayılan sayfayı kullanın.", "custom_route": "Özel anasayfa yolu", "custom_route_help": "Herhangi bir eğik çizgi olmadan, burada bir yol adını girin (örneğin \"yeniler\" veya \"popüler\")", - "sso.title": "Tek giriş servisleri", + "sso.title": "Tek tuşla giriş uygulamaları", "sso.associated": "Birleştirilmiş", "sso.not-associated": "Birleştirmek için buraya tıklayın", "sso.dissociate": "Ayrış", @@ -144,7 +144,7 @@ "info.ban-history": "Yasaklama Olayları", "info.no-ban-history": "Bu kullanıcı hiç yasaklanmadı", "info.banned-until": "Yasaklama süresi %1", - "info.banned-permanently": "Kalıcı yasakla", + "info.banned-permanently": "Kalıcı şekilde yasakla", "info.banned-reason-label": "Gerekçe", "info.banned-no-reason": "Gerekçe belirtilmedi.", "info.username-history": "Kullanıcı Adı Geçmişi", diff --git a/public/language/uk/admin/development/info.json b/public/language/uk/admin/development/info.json index c3d39a1c5e..34fa8889f9 100644 --- a/public/language/uk/admin/development/info.json +++ b/public/language/uk/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Інформація - Ви знаходитесь на %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 вузлів відповіли за %2мс!", "host": "host", "pid": "pid", diff --git a/public/language/uk/admin/manage/tags.json b/public/language/uk/admin/manage/tags.json index 2252ce563b..755115bfd6 100644 --- a/public/language/uk/admin/manage/tags.json +++ b/public/language/uk/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Натисніть тут щоб перейти на сторінку налаштування тегів.", "name": "Назва тегу", - "alerts.editing-multiple": "Редагування декількох тегів", - "alerts.editing-x": "Редагування тегу \"%1\"", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Бажаєте видалити декілька тегів?", "alerts.update-success": "Тег оновлено!" } \ No newline at end of file diff --git a/public/language/uk/admin/settings/advanced.json b/public/language/uk/admin/settings/advanced.json index 467cb341b6..65dfd2dfbb 100644 --- a/public/language/uk/admin/settings/advanced.json +++ b/public/language/uk/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "Strict Transport Security", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/uk/admin/settings/general.json b/public/language/uk/admin/settings/general.json index 3f2c712034..74d8bd9e08 100644 --- a/public/language/uk/admin/settings/general.json +++ b/public/language/uk/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Налаштування сайту", "title": "Назва сайту", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "URL заголовку сайту", "title.url-help": "По кліку на заголовок, спрямовувати користувача за цією адресою. Якщо залишити порожнім, користувач буде спрямований в корінь форуму.", @@ -31,5 +33,9 @@ "outgoing-links": "Зовнішні посилання", "outgoing-links.warning-page": "Використовувати сторінку попередження про зовнішній перехід", "search-default-sort-by": "Типовий порядок результатів пошуку", - "outgoing-links.whitelist": "Безпечні домени для пропуску сторінки попередження" -} \ No newline at end of file + "outgoing-links.whitelist": "Безпечні домени для пропуску сторінки попередження", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/uk/admin/settings/group.json b/public/language/uk/admin/settings/group.json index 183d361a88..a634adf133 100644 --- a/public/language/uk/admin/settings/group.json +++ b/public/language/uk/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Приватні групи", "private-groups.help": "Якщо увімкнено, приєднання до групи вимагає підтвердження власника (По замовчуванню: увімкнено)", "private-groups.warning": "Увага! Якщо ця опція вимикається і у вас є приватні групи, вони автоматично стають публічними.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "Цей прапорець може бути використаний, щоб надати можливість користувачам обирати кілька бейджів груп, необхідна підтримка цієї можливості темою.", "max-name-length": "Максимальна довжина імені групи", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/uk/admin/settings/user.json b/public/language/uk/admin/settings/user.json index 7f8b1d3b38..a4d69987fd 100644 --- a/public/language/uk/admin/settings/user.json +++ b/public/language/uk/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Тільки електронну пошту", "account-settings": "Налаштування акаунту", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Вимкнути зміну імені користувача", "disable-email-changes": "Вимкнути зміну електронної пошти", "disable-password-changes": "Вимкнути зміну пароля", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/uk/modules.json b/public/language/uk/modules.json index a074c3fd82..0f79692b9d 100644 --- a/public/language/uk/modules.json +++ b/public/language/uk/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Нещодавні чати", "chat.contacts": "Контакти", "chat.message-history": "Історія повідомлень", + "chat.message-deleted": "Message Deleted", "chat.options": "Параметри чату", "chat.pop-out": "Залишити розмову", "chat.minimize": "Згорнути", diff --git a/public/language/uk/topic.json b/public/language/uk/topic.json index c3e7484c52..0a22ffa3ab 100644 --- a/public/language/uk/topic.json +++ b/public/language/uk/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Натисніть на пости, які ви хочете перемістити", "change_owner_instruction": "Клікніть на дописи які ви хочете призначити іншому користувачу", "composer.title_placeholder": "Уведіть заголовок теми...", - "composer.handle_placeholder": "Ім'я", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Скасувати", "composer.submit": "Надіслати", "composer.replying_to": "Відповідь для %1", diff --git a/public/language/vi/admin/development/info.json b/public/language/vi/admin/development/info.json index c095a9718b..da7e6b547a 100644 --- a/public/language/vi/admin/development/info.json +++ b/public/language/vi/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "Info - You are on %1:%2", + "you-are-on": "You are on %1:%2", + "ip": "IP %1", "nodes-responded": "%1 nodes responded within %2ms!", "host": "host", "pid": "pid", diff --git a/public/language/vi/admin/manage/tags.json b/public/language/vi/admin/manage/tags.json index df597a6166..6d7cbdb289 100644 --- a/public/language/vi/admin/manage/tags.json +++ b/public/language/vi/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "Click here to visit the tag settings page.", "name": "Tag Name", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", + "alerts.editing": "Editing tag(s)", "alerts.confirm-delete": "Do you want to delete the selected tags?", "alerts.update-success": "Tag Updated!" } \ No newline at end of file diff --git a/public/language/vi/admin/settings/advanced.json b/public/language/vi/admin/settings/advanced.json index eaa1c31801..1ea8e1d3b6 100644 --- a/public/language/vi/admin/settings/advanced.json +++ b/public/language/vi/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "An ninh giao thông nghiêm ngặt", "hsts.enabled": "Enabled HSTS (recommended)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "Include subdomains in HSTS header", "hsts.preload": "Allow preloading of HSTS header", "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", diff --git a/public/language/vi/admin/settings/general.json b/public/language/vi/admin/settings/general.json index 948123f7cb..4c37897b61 100644 --- a/public/language/vi/admin/settings/general.json +++ b/public/language/vi/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "Site Settings", "title": "Site Title", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "URL", "title.url-placeholder": "The URL of the site title", "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", @@ -31,5 +33,9 @@ "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 + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/vi/admin/settings/group.json b/public/language/vi/admin/settings/group.json index a28bd10c00..f13933ea7e 100644 --- a/public/language/vi/admin/settings/group.json +++ b/public/language/vi/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "Private Groups", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", + "allow-multiple-badges": "Allow Multiple Badges", "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", "max-name-length": "Maximum Group Name Length", "max-title-length": "Maximum Group Title Length", diff --git a/public/language/vi/admin/settings/user.json b/public/language/vi/admin/settings/user.json index 9f029bf777..b94dc98870 100644 --- a/public/language/vi/admin/settings/user.json +++ b/public/language/vi/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "Disable username changes", "disable-email-changes": "Disable email changes", "disable-password-changes": "Disable password changes", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "Watching", "categoryWatchState.notwatching": "Not Watching", "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file +} diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index a3225aaa9d..a999300b1a 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Vừa chat", "chat.contacts": "Liên hệ", "chat.message-history": "Lịch sử tin nhắn", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Bật cửa sổ chat", "chat.minimize": "Minimize", diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 6f0ce364a5..d53527a79b 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "Nhấp chuột vào bài viết bạn muốn di chuyển", "change_owner_instruction": "Bấm vào bài viết bạn muốn chỉ định cho người dùng khác", "composer.title_placeholder": "Nhập tiêu đề cho chủ đề của bạn tại đây...", - "composer.handle_placeholder": "Tên", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "Huỷ bỏ", "composer.submit": "Gửi", "composer.replying_to": "Đang trả lời %1", diff --git a/public/language/zh-CN/admin/development/info.json b/public/language/zh-CN/admin/development/info.json index b419eaf0cf..48b0ecbf8d 100644 --- a/public/language/zh-CN/admin/development/info.json +++ b/public/language/zh-CN/admin/development/info.json @@ -1,5 +1,6 @@ { - "you-are-on": "信息 - 您正在 %1:%2 上", + "you-are-on": "你正使用 %1:%2", + "ip": "IP %1", "nodes-responded": "%1个节点在%2ms内响应!", "host": "主机", "pid": "pid", diff --git a/public/language/zh-CN/admin/extend/plugins.json b/public/language/zh-CN/admin/extend/plugins.json index f5cdf59dbc..de1f3906a0 100644 --- a/public/language/zh-CN/admin/extend/plugins.json +++ b/public/language/zh-CN/admin/extend/plugins.json @@ -12,7 +12,7 @@ "submit-anonymous-usage": "提交匿名插件使用数据。", "reorder-plugins": "重新排序插件", "order-active": "排序生效插件", - "dev-interested": "有兴趣为NodeBB开发插件?", + "dev-interested": "有兴趣为 NodeBB 开发插件?", "docs-info": "有关插件创作的完整文档可以在 NodeBB 文档中找到。", "order.description": "部分插件需要在其它插件启用之后才能完美运作。", @@ -30,7 +30,7 @@ "plugin-item.more-info": "更多信息:", "plugin-item.unknown": "未知", "plugin-item.unknown-explanation": "无法确认该插件的状态,可能由于配置错误造成。", - "plugin-item.compatible": "此插件有1%适合NodeBB", + "plugin-item.compatible": "此插件兼容 NodeBB %1", "plugin-item.not-compatible": "此插件没有兼容性数据,请确保在生产环境中安装之前它可以正常工作。", "alert.enabled": "插件已启用", diff --git a/public/language/zh-CN/admin/manage/digest.json b/public/language/zh-CN/admin/manage/digest.json index 1a9554af43..44998663ea 100644 --- a/public/language/zh-CN/admin/manage/digest.json +++ b/public/language/zh-CN/admin/manage/digest.json @@ -1,6 +1,6 @@ { "lead": "以下是摘要发送状态及时间列表", - "disclaimer": "请注意,由于email技术本身的原因,邮件发送无法保证送达。有很多因素都会导致邮件无法送达用户的收件箱,比如服务拒绝,IP地址黑名单,DNS记录DKIM/SPF/DMARC类型配置。", + "disclaimer": "请注意,由于 Email 技术本身的原因,邮件不一定能保证送达。有很多因素都会导致邮件无法到达用户的收件箱,比如发件服务器的信誉、IP 地址黑名单、DNS 的 DKIM/SPF/DMARC 配置等。", "disclaimer-continued": "成功发送意味道消息被 NodeBB 成功发送且被接收人服务器收到。但这并不等同于邮件发送到了收件箱中。为了确保消息可以准确送达,我们建议使用第三方的邮件服务,例如SendGrid。", "user": "用户", @@ -17,5 +17,5 @@ "null": "从不", "manual-run": "手动运行摘要:", - "no-delivery-data": "找不到递交的数据" + "no-delivery-data": "找不到发件数据" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/manage/tags.json b/public/language/zh-CN/admin/manage/tags.json index 915eff4135..2bf4ae6d40 100644 --- a/public/language/zh-CN/admin/manage/tags.json +++ b/public/language/zh-CN/admin/manage/tags.json @@ -12,8 +12,7 @@ "settings": "点击此处 访问话题设置页面。", "name": "话题名", - "alerts.editing-multiple": "编辑多项话题", - "alerts.editing-x": "编辑 \"%1\" 话题", + "alerts.editing": "编辑标签", "alerts.confirm-delete": "是否要删除所选话题?", "alerts.update-success": "话题已更新!" } \ No newline at end of file diff --git a/public/language/zh-CN/admin/settings/advanced.json b/public/language/zh-CN/admin/settings/advanced.json index 447c4e688e..077516e36e 100644 --- a/public/language/zh-CN/admin/settings/advanced.json +++ b/public/language/zh-CN/admin/settings/advanced.json @@ -15,6 +15,7 @@ "headers.acah": "Access-Control-Allow-Headers", "hsts": "严格安全传输(HSTS)", "hsts.enabled": "启用HSTS(推荐)", + "hsts.maxAge": "HSTS Max Age", "hsts.subdomains": "HSTS头信息包含的域名", "hsts.preload": "允许在HSTS头信息中预加载(preloading)", "hsts.help": "如果启用此项,站点将会向浏览器发送HSTS头信息。您可以设置是否为子域名开启HSTS,以及HSTS头信息中是否包含预加载标志(preload参数)如果您不了解HSTS,可以忽略此项设置。了解详情 ", diff --git a/public/language/zh-CN/admin/settings/general.json b/public/language/zh-CN/admin/settings/general.json index 6683a0b76b..ec9a0f97e7 100644 --- a/public/language/zh-CN/admin/settings/general.json +++ b/public/language/zh-CN/admin/settings/general.json @@ -1,6 +1,8 @@ { "site-settings": "站点设置", "title": "站点标题", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "网址", "title.url-placeholder": "网站标题链接", "title.url-help": "当标题被点击,用户将跳转到该地址。如果留空,用户将跳转到论坛首页。", @@ -31,5 +33,9 @@ "outgoing-links": "站外链接", "outgoing-links.warning-page": "使用站外链接警告页", "search-default-sort-by": "默认搜索排序", - "outgoing-links.whitelist": "添加域名到白名单以绕过警告页面" -} \ No newline at end of file + "outgoing-links.whitelist": "添加域名到白名单以绕过警告页面", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/zh-CN/admin/settings/group.json b/public/language/zh-CN/admin/settings/group.json index fec69063a0..6ffbe28e15 100644 --- a/public/language/zh-CN/admin/settings/group.json +++ b/public/language/zh-CN/admin/settings/group.json @@ -3,6 +3,7 @@ "private-groups": "私有群组", "private-groups.help": "启用此选项后,加入用户组需要群组所有者审批(默认启用)。", "private-groups.warning": "注意!如果这个选项未启用并且你有私有群组,那么你的群组将变为公共的。", + "allow-multiple-badges": "允许多种奖章", "allow-multiple-badges-help": "启用此选项后,用户可以选择显示多个群组徽章,需要主题支持。", "max-name-length": "群组名字的最大长度", "max-title-length": "群组标题最大长度", diff --git a/public/language/zh-CN/admin/settings/user.json b/public/language/zh-CN/admin/settings/user.json index ca52ce199c..37eb05573c 100644 --- a/public/language/zh-CN/admin/settings/user.json +++ b/public/language/zh-CN/admin/settings/user.json @@ -9,7 +9,7 @@ "allow-login-with.email": "仅限邮箱", "account-settings": "用户设置", "gdpr_enabled": "启用通用数据保护条例(GDPR)许可的个人信息收集", - "gdpr_enabled_help": "当启用时,所有的新注册用户需要明确同意允许数据采集和在通用数据保护协议(GDPR)保护下的使用。注意:开启GDPR不一定要之前已经存在的用户同意。在这之前,你需要去安装GDPR插件。", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", "disable-username-changes": "禁用修改用户名", "disable-email-changes": "禁用修改邮箱", "disable-password-changes": "禁用修改密码", @@ -76,4 +76,4 @@ "categoryWatchState.watching": "已关注", "categoryWatchState.notwatching": "未关注", "categoryWatchState.ignoring": "已忽略" -} \ No newline at end of file +} diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json index 4d305dab16..a33719ce33 100644 --- a/public/language/zh-CN/modules.json +++ b/public/language/zh-CN/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "最近聊天", "chat.contacts": "联系人", "chat.message-history": "消息历史", + "chat.message-deleted": "消息已删除", "chat.options": "聊天配置", "chat.pop-out": "弹出聊天窗口", "chat.minimize": "最小化", diff --git a/public/language/zh-CN/topic.json b/public/language/zh-CN/topic.json index 1af292af95..4676bec671 100644 --- a/public/language/zh-CN/topic.json +++ b/public/language/zh-CN/topic.json @@ -103,7 +103,7 @@ "move_posts_instruction": "点击您想要移动的帖子", "change_owner_instruction": "点击您想转移给其他用户的帖子", "composer.title_placeholder": "在此输入您主题的标题...", - "composer.handle_placeholder": "姓名", + "composer.handle_placeholder": "Enter your name/handle here", "composer.discard": "撤销", "composer.submit": "提交", "composer.replying_to": "正在回复 %1", diff --git a/public/language/zh-TW/admin/admin.json b/public/language/zh-TW/admin/admin.json index 1f7755a297..2353c9cb5f 100644 --- a/public/language/zh-TW/admin/admin.json +++ b/public/language/zh-TW/admin/admin.json @@ -1,7 +1,7 @@ { - "alert.confirm-rebuild-and-restart": "確認重建並重啟NodeBB?", - "alert.confirm-restart": "確認重啟NodeBB?", + "alert.confirm-rebuild-and-restart": "您確定要部署並重啟 NodeBB 嗎?", + "alert.confirm-restart": "您確定要重啟 NodeBB 嗎?", - "acp-title": "%1 | NodeBB 管理控制面板", + "acp-title": "%1 | NodeBB 管理控制台", "settings-header-contents": "内容" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/advanced/cache.json b/public/language/zh-TW/admin/advanced/cache.json index 52db0c3668..1ae120af3c 100644 --- a/public/language/zh-TW/admin/advanced/cache.json +++ b/public/language/zh-TW/admin/advanced/cache.json @@ -1,11 +1,11 @@ { - "post-cache": "文章快取", - "posts-in-cache": "暫存中的文章", - "average-post-size": "平均文章大小", - "length-to-max": "長度 / 最大", - "percent-full": "%1% 已滿", - "post-cache-size": "文章快取大小", - "items-in-cache": "暫存中的項目", + "post-cache": "貼文快取", + "posts-in-cache": "快取中的貼文", + "average-post-size": "平均貼文長度", + "length-to-max": "長度 / 最大值", + "percent-full": "%1% 容量", + "post-cache-size": "貼文快取大小", + "items-in-cache": "快取中的項目數量", "control-panel": "控制面板", "update-settings": "更新快取設定" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/advanced/database.json b/public/language/zh-TW/admin/advanced/database.json index 99b81b487b..03bf08fc14 100644 --- a/public/language/zh-TW/admin/advanced/database.json +++ b/public/language/zh-TW/admin/advanced/database.json @@ -2,51 +2,51 @@ "x-b": "%1 b", "x-mb": "%1 mb", "x-gb": "%1 gb", - "uptime-seconds": "正常運作秒數", - "uptime-days": "正常運作天數", + "uptime-seconds": "運行秒數", + "uptime-days": "運行天數", "mongo": "Mongo", "mongo.version": "MongoDB 版本", "mongo.storage-engine": "儲存引擎", - "mongo.collections": "收集", - "mongo.objects": "物件", + "mongo.collections": "集合", + "mongo.objects": "物件數量", "mongo.avg-object-size": "平均物件大小", - "mongo.data-size": "資料大小", + "mongo.data-size": "資酪大小", "mongo.storage-size": "儲存大小", "mongo.index-size": "索引大小", "mongo.file-size": "檔案大小", "mongo.resident-memory": "常駐記憶體", "mongo.virtual-memory": "虛擬記憶體", - "mongo.mapped-memory": "映射記憶體", - "mongo.bytes-in": "Bytes In", - "mongo.bytes-out": "Bytes Out", - "mongo.num-requests": "Number of Requests", + "mongo.mapped-memory": "已映射記憶體", + "mongo.bytes-in": "位元組輸入", + "mongo.bytes-out": "位元組輸出", + "mongo.num-requests": "請求數量", "mongo.raw-info": "MongoDB 原始資訊", - "mongo.unauthorized": "NodeBB was unable to query the MongoDB database for relevant statistics. Please ensure that the user in use by NodeBB contains the "clusterMonitor" role for the "admin" database.", + "mongo.unauthorized": "NodeBB 無法從 MongoDB 資料庫請求相應的統計資料。請確保 NodeBB 使用的連線帳戶具有"admin"資料庫的"clusterMonitor"角色。", "redis": "Redis", "redis.version": "Redis 版本", - "redis.keys": "Keys", - "redis.expires": "Expires", - "redis.avg-ttl": "Average TTL", - "redis.connected-clients": "已連接的用戶端", - "redis.connected-slaves": "已連接的從屬端", - "redis.blocked-clients": "已阻擋的用戶端", - "redis.used-memory": "已使用的記憶體", - "redis.memory-frag-ratio": "記憶體碎片比例", - "redis.total-connections-recieved": "已接收的連線數量", - "redis.total-commands-processed": "已處裡的命令數量", - "redis.iops": "每秒操作數量", - "redis.iinput": "Instantaneous Input Per Second", - "redis.ioutput": "Instantaneous Output Per Second", - "redis.total-input": "Total Input", - "redis.total-output": "Total Ouput", + "redis.keys": "鍵數量", + "redis.expires": "有效期", + "redis.avg-ttl": "平均生存時間", + "redis.connected-clients": "已連接客戶端", + "redis.connected-slaves": "已連接從", + "redis.blocked-clients": "封鎖的客戶端", + "redis.used-memory": "已使用記憶體", + "redis.memory-frag-ratio": "記憶體碎片比率", + "redis.total-connections-recieved": "已接收的連線總數", + "redis.total-commands-processed": "已執行命令總數", + "redis.iops": "每秒即時操作數", + "redis.iinput": "每秒即時輸入", + "redis.ioutput": "每秒即時輸出", + "redis.total-input": "總輸入", + "redis.total-output": "總輸出", - "redis.keyspace-hits": "鍵空間命中次數", - "redis.keyspace-misses": "鍵空間未命中次數", - "redis.raw-info": "Redis 原始資料", + "redis.keyspace-hits": "Keyspace 命中", + "redis.keyspace-misses": "Keyspace 未命中", + "redis.raw-info": "Redis 原始資訊", "postgres": "Postgres", - "postgres.version": "PostgreSQL Version", - "postgres.raw-info": "Postgres Raw Info" + "postgres.version": "PostgreSQL 版本", + "postgres.raw-info": "Postgres 原始資訊" } diff --git a/public/language/zh-TW/admin/advanced/errors.json b/public/language/zh-TW/admin/advanced/errors.json index 1b29af32d4..547effa250 100644 --- a/public/language/zh-TW/admin/advanced/errors.json +++ b/public/language/zh-TW/admin/advanced/errors.json @@ -1,14 +1,14 @@ { - "figure-x": "表 %1", + "figure-x": "數量 %1", "error-events-per-day": "%1 事件/天", - "error.404": "404 伺服器無法回應", - "error.503": "503 伺服器忙碌中", + "error.404": "404 頁面不存在", + "error.503": "503 服務不可用", "manage-error-log": "管理錯誤日誌", - "export-error-log": "匯出錯誤日誌 (CSV)", - "clear-error-log": "清除錯誤日誌", - "route": "路由", - "count": "計數", - "no-routes-not-found": "萬歲!沒有404 錯誤!", - "clear404-confirm": "您確定要清除404錯誤日誌嗎?", - "clear404-success": "\"404 Not Found\" 錯誤已清除" + "export-error-log": "導出錯誤日誌 (.csv)", + "clear-error-log": "清空錯誤日誌", + "route": "路徑", + "count": "次數", + "no-routes-not-found": "恭喜!沒有404錯誤!", + "clear404-confirm": "確認清除404錯誤日誌?", + "clear404-success": "“404 頁面不存在” 錯誤已被清空" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/advanced/events.json b/public/language/zh-TW/admin/advanced/events.json index f3f37c0c78..57c5645790 100644 --- a/public/language/zh-TW/admin/advanced/events.json +++ b/public/language/zh-TW/admin/advanced/events.json @@ -1,11 +1,11 @@ { "events": "事件", - "no-events": "沒有事件", + "no-events": "暫無事件", "control-panel": "事件控制面板", - "filters": "Filters", - "filters-apply": "Apply Filters", - "filter-type": "Event Type", - "filter-start": "Start Date", - "filter-end": "End Date", - "filter-perPage": "Per Page" + "filters": "過濾器", + "filters-apply": "應用過濾器", + "filter-type": "事件類型", + "filter-start": "開始時間", + "filter-end": "結束時間", + "filter-perPage": "每頁" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/advanced/logs.json b/public/language/zh-TW/admin/advanced/logs.json index c01e1dce11..76d874ac36 100644 --- a/public/language/zh-TW/admin/advanced/logs.json +++ b/public/language/zh-TW/admin/advanced/logs.json @@ -2,6 +2,6 @@ "logs": "日誌", "control-panel": "日誌控制面板", "reload": "重載日誌", - "clear": "清除日誌", - "clear-success": "清除日誌!" + "clear": "清空日誌", + "clear-success": "日誌已清空!" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/appearance/customise.json b/public/language/zh-TW/admin/appearance/customise.json index 51722b18a8..9797ee69c7 100644 --- a/public/language/zh-TW/admin/appearance/customise.json +++ b/public/language/zh-TW/admin/appearance/customise.json @@ -1,16 +1,16 @@ { - "custom-css": "自定義 CSS/LESS", - "custom-css.description": "在這裡輸入你想應用于所有其他風格的 CSS/LESS,", - "custom-css.enable": "啟用自定義 CSS/LESS", + "custom-css": "自訂 CSS/LESS", + "custom-css.description": "在這裡輸入您想應用於所有其它風格的 CSS/LESS,", + "custom-css.enable": "啟用自訂 CSS/LESS", - "custom-js": "自定義Javascript", - "custom-js.description": "在這裡輸入你想應用于完全加載頁后執行的 javascript。", - "custom-js.enable": "啟用自定義 Javascript", + "custom-js": "自訂 Javascript", + "custom-js.description": "在這裡輸入您想在頁面加載完成後執行的 Javascript 程式碼。", + "custom-js.enable": "啟用自訂 Javascript", - "custom-header": "自定義頭標", - "custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <head> section of your forum's markup. Script tags are allowed, but are discouraged, as the Custom Javascript tab is available.", - "custom-header.enable": "開啟客製化header", + "custom-header": "自訂 Header", + "custom-header.description": "在這裡輸入自訂的 HTML 程式碼 (如 Meta Tags 等),這些程式碼會被添加到論壇的 <head>部分。 您可以在這裡使用 Script 標籤,但我們更鼓勵您將您的 JavaScript 寫到 自訂 Javascript 中", + "custom-header.enable": "啟用自訂 Header", - "custom-css.livereload": "開啟動態重載 (live reload)", - "custom-css.livereload.description": "Enable this to force all sessions on every device under your account to refresh whenever you click save" + "custom-css.livereload": "啟用動態重載", + "custom-css.livereload.description": "啟用此功能可以在您點擊儲存時強制您帳戶下的每個設備上的所有會話進行重載" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/appearance/skins.json b/public/language/zh-TW/admin/appearance/skins.json index 6a50dd6f17..ba0bf3b7cf 100644 --- a/public/language/zh-TW/admin/appearance/skins.json +++ b/public/language/zh-TW/admin/appearance/skins.json @@ -1,9 +1,9 @@ { - "loading": "正在載入外觀...", + "loading": "正在加載配色...", "homepage": "首頁", - "select-skin": "選擇外觀", - "current-skin": "正在使用的外觀", - "skin-updated": "外觀已上傳", - "applied-success": "%1 個外觀已套用", - "revert-success": "外觀已恢復" + "select-skin": "選擇配色", + "current-skin": "當前配色", + "skin-updated": "配色已更新", + "applied-success": "%1 配色已成功套用", + "revert-success": "配色已恢復到基礎顏色" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/appearance/themes.json b/public/language/zh-TW/admin/appearance/themes.json index 960011ef91..23b7c948db 100644 --- a/public/language/zh-TW/admin/appearance/themes.json +++ b/public/language/zh-TW/admin/appearance/themes.json @@ -1,11 +1,11 @@ { - "checking-for-installed": "正在載入已安裝的主題...", + "checking-for-installed": "正在檢查已安裝的佈景主題...", "homepage": "首頁", - "select-theme": "選擇主題", - "current-theme": "目前的主題", - "no-themes": "沒有找到任何已安裝的主題", - "revert-confirm": "你確定要回復預設的主題嗎?", - "theme-changed": "主題已更換", - "revert-success": "已經成功回復為預設主題。", - "restart-to-activate": "Please rebuild and restart your NodeBB to fully activate this theme." + "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/zh-TW/admin/development/info.json b/public/language/zh-TW/admin/development/info.json index f9ec6ed25c..e70189bc73 100644 --- a/public/language/zh-TW/admin/development/info.json +++ b/public/language/zh-TW/admin/development/info.json @@ -1,19 +1,20 @@ { - "you-are-on": "Info - 你現在在 %1:%2", - "nodes-responded": "%1 nodes 在 %2ms! 內反應", - "host": "host", + "you-are-on": "您的位址 %1:%2", + "ip": "IP %1", + "nodes-responded": "%1個節點在%2ms內響應!", + "host": "主機", "pid": "pid", "nodejs": "nodejs", - "online": "online", + "online": "在線", "git": "git", - "memory": "memory", + "memory": "記憶體", "load": "系統負載", - "cpu-usage": "CPU用量", - "uptime": "Uptime", + "cpu-usage": "CPU 使用情況", + "uptime": "運行時間", - "registered": "註冊", - "sockets": "Sockets", + "registered": "已註冊", + "sockets": "網路接口", "guests": "訪客", - "info": "Info" + "info": "資訊" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/development/logger.json b/public/language/zh-TW/admin/development/logger.json index 6ab9558149..e7f398272b 100644 --- a/public/language/zh-TW/admin/development/logger.json +++ b/public/language/zh-TW/admin/development/logger.json @@ -1,12 +1,12 @@ { - "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-socket": "Enable socket.io event logging", - "file-path": "Path to log file", - "file-path-placeholder": "/path/to/log/file.log ::: leave blank to log to your terminal", + "logger-settings": "日誌記錄器設定", + "description": "啟用此選項後,日誌會在您的終端裡顯示。如果您註明了檔案路徑,日誌會被保存到該檔案中。HTTP 日誌可以幫助您收集論壇被誰,何時,以及什麼內容被訪問等統計資訊。在此基礎上,我們還提供 socket.io 事件日誌。結合 socket.io 日誌和 redis-cli 監控器,學習 NodeBB 的內部構造會更加方便。", + "explanation": "勾選或反勾選日誌設定項即可啟用或禁用相應設定。無需重啟。", + "enable-http": "啟用 HTTP 日誌", + "enable-socket": "啟用 socket.io 事件日誌", + "file-path": "日誌檔案路徑", + "file-path-placeholder": "如 /path/to/log/file.log ::: 如想在終端中顯示日誌請留空此項", - "control-panel": "Logger Control Panel", - "update-settings": "Update Logger Settings" + "control-panel": "日誌記錄器控制面板", + "update-settings": "更新日誌記錄器設定" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/extend/plugins.json b/public/language/zh-TW/admin/extend/plugins.json index b611874eff..3b6e5c2b66 100644 --- a/public/language/zh-TW/admin/extend/plugins.json +++ b/public/language/zh-TW/admin/extend/plugins.json @@ -2,55 +2,55 @@ "installed": "已安裝", "active": "已啟用", "inactive": "未啟用", - "out-of-date": "需要更新", - "none-found": "沒有找到外掛。", - "none-active": "沒有啟用中的外掛。", - "find-plugins": "搜尋外掛", + "out-of-date": "已過期", + "none-found": "無外掛。", + "none-active": "無生效外掛", + "find-plugins": "尋找外掛", - "plugin-search": "搜尋外掛", - "plugin-search-placeholder": "搜尋想找的外掛...", - "submit-anonymous-usage": "Submit anonymous plugin usage data.", - "reorder-plugins": "重新排序", - "order-active": "排序已啟用的外掛", - "dev-interested": "對開發NodeBB的外掛有興趣嗎?", - "docs-info": "Full documentation regarding plugin authoring can be found in the NodeBB Docs Portal.", + "plugin-search": "外掛搜索", + "plugin-search-placeholder": "搜索外掛...", + "submit-anonymous-usage": "提交匿名外掛使用資料。", + "reorder-plugins": "重新排序外掛", + "order-active": "排序生效外掛", + "dev-interested": "有興趣為 NodeBB 開發外掛?", + "docs-info": "有關外掛創作的完整文件可以在 NodeBB 文件中找到。", - "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": "部分外掛需要在其它外掛啟用之後才能完整運作。", + "order.explanation": "外掛將按照以下順序載入,從上至下。", "plugin-item.themes": "佈景主題", "plugin-item.deactivate": "停用", "plugin-item.activate": "啟用", "plugin-item.install": "安裝", - "plugin-item.uninstall": "解除安裝", + "plugin-item.uninstall": "卸載", "plugin-item.settings": "設定", "plugin-item.installed": "已安裝", "plugin-item.latest": "最新", "plugin-item.upgrade": "升級", "plugin-item.more-info": "更多資訊:", "plugin-item.unknown": "未知", - "plugin-item.unknown-explanation": "無法取得此外掛的狀態,可能是由於設置錯誤。", - "plugin-item.compatible": "This plugin works on NodeBB %1", - "plugin-item.not-compatible": "This plugin has no compatibility data, make sure it works before installing on your production environment.", + "plugin-item.unknown-explanation": "無法確認該外掛的狀態,可能由於設定錯誤造成。", + "plugin-item.compatible": "此外掛適合 NodeBB %1", + "plugin-item.not-compatible": "此外掛沒有相容資料,請確保在生產環境中安裝之前它可以正常工作。", "alert.enabled": "外掛已啟用", - "alert.disabled": "外掛已停用", + "alert.disabled": "外掛已禁用", "alert.upgraded": "外掛已升級", "alert.installed": "外掛已安裝", - "alert.uninstalled": "外掛已移除", - "alert.activate-success": "請重新啟動你的NodeBB來完成外掛的啟用", - "alert.deactivate-success": "外掛已成功停用", - "alert.upgrade-success": "Please rebuild and restart your NodeBB to fully upgrade this plugin.", - "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.

", - "alert.possibly-incompatible": "

No Compatibility Information Found

This plugin did not specify a specific version for installation given your NodeBB version. Full compatibility cannot be guaranteed, and may cause your NodeBB to no longer start properly.

In the event that NodeBB cannot boot properly:

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

Continue installation of latest version of this plugin?

", - "alert.reorder": "Plugins Re-ordered", - "alert.reorder-success": "Please rebuild and restart your NodeBB to fully complete the process.", + "alert.uninstalled": "外掛已卸載", + "alert.activate-success": "請 部署 & 重啟 NodeBB 來完全啟用此外掛", + "alert.deactivate-success": "外掛停用成功", + "alert.upgrade-success": "請部署並重啟您的 NodeBB 來完成更新此外掛。", + "alert.install-success": "外掛安裝成功,請啟用外掛。", + "alert.uninstall-success": "外掛已成功被停用且卸載。", + "alert.suggest-error": "

NodeBB 聯繫不到套件管理器, 繼續安裝最新版本?

伺服器返回 (%1): %2
", + "alert.package-manager-unreachable": "

NodeBB 聯繫不到套件管理器,暫時不建議升級。

", + "alert.incompatible": "

您目前安裝的 NodeBB 版本(v%1) 只支持到此外掛的 v%2 版本。如需要此外掛更加新的版本請先升級 NodeBB。

", + "alert.possibly-incompatible": "

未找到相容性資料

此外掛未註明對應的 NodeBB 版本。可能會產生相容性問題,導致 NodeBB 無法正常啟動。

NodeBB 無法正常啟動時請運行以下命令:

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

是否繼續安裝此插件的最新版本?

", + "alert.reorder": "插件已重新排序", + "alert.reorder-success": "請部署並重啟您的 NodeBB 來完成此流程。", - "license.title": "Plugin License Information", - "license.intro": "The plugin %1 is licensed under the %2. Please read and understand the license terms prior to activating this plugin.", - "license.cta": "Do you wish to continue with activating this plugin?" + "license.title": "外掛授權資料", + "license.intro": "外掛 %1 在 %2 下獲得許可。請在啟用此插件之前閱讀,確認授權條款。", + "license.cta": "您希望繼續使用此外掛嗎?" } diff --git a/public/language/zh-TW/admin/extend/rewards.json b/public/language/zh-TW/admin/extend/rewards.json index eb554e441c..19ef749362 100644 --- a/public/language/zh-TW/admin/extend/rewards.json +++ b/public/language/zh-TW/admin/extend/rewards.json @@ -1,17 +1,17 @@ { "rewards": "獎勵", - "condition-if-users": "若用戶的", - "condition-is": "為:", - "condition-then": "那麼:", - "max-claims": "獎勵可以領取的次數", - "zero-infinite": "輸入 0 表示不限", + "condition-if-users": "如果使用者的", + "condition-is": "是:", + "condition-then": "則:", + "max-claims": "可獲取獎勵的次數", + "zero-infinite": "無限制請輸入0", "delete": "刪除", "enable": "啟用", - "disable": "停用", - "control-panel": "獎勵設定", - "new-reward": "新的獎勵", + "disable": "禁用", + "control-panel": "獎勵控制", + "new-reward": "新獎勵", - "alert.delete-success": "Successfully deleted reward", - "alert.no-inputs-found": "Illegal reward - no inputs found!", - "alert.save-success": "Successfully saved rewards" + "alert.delete-success": "已成功刪除獎勵", + "alert.no-inputs-found": "非法獎勵 - 輸入為空!", + "alert.save-success": "已成功儲存獎勵" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/extend/widgets.json b/public/language/zh-TW/admin/extend/widgets.json index f2255b9395..3ebc537db9 100644 --- a/public/language/zh-TW/admin/extend/widgets.json +++ b/public/language/zh-TW/admin/extend/widgets.json @@ -1,30 +1,30 @@ { - "available": "Available Widgets", - "explanation": "Select a widget from the dropdown menu and then drag and drop it into a template's widget area on the left.", - "none-installed": "No widgets found! Activate the essential widgets plugin in the plugins control panel.", - "clone-from": "Clone widgets from", - "containers.available": "Available Containers", - "containers.explanation": "Drag and drop on top of any active widget", - "containers.none": "None", + "available": "可用的小工具", + "explanation": "從下拉選單中選擇一個小工具並拖放到樣板左邊的小工具區域。", + "none-installed": "未發現小工具!請在外掛控制面板中啟用必要的小工具插件。", + "clone-from": "從小工具複製", + "containers.available": "可用的容器", + "containers.explanation": "拖放到任意生效中的小工具上", + "containers.none": "無", "container.well": "Well", - "container.jumbotron": "Jumbotron", - "container.panel": "Panel", - "container.panel-header": "Panel Header", - "container.panel-body": "Panel Body", - "container.alert": "Alert", + "container.jumbotron": "超大屏幕", + "container.panel": "面板", + "container.panel-header": "面板標題", + "container.panel-body": "面板內容", + "container.alert": "警示", - "alert.confirm-delete": "Are you sure you wish to delete this widget?", - "alert.updated": "Widgets Updated", - "alert.update-success": "Successfully updated widgets", - "alert.clone-success": "Successfully cloned widgets", + "alert.confirm-delete": "確認刪除此小工具?", + "alert.updated": "小工具更新", + "alert.update-success": "已成功更新小工具", + "alert.clone-success": "成功複製小工具", - "error.select-clone": "Please select a page to clone from", + "error.select-clone": "請選擇一個頁面進行複製", - "title": "Title", - "title.placeholder": "Title (only shown on some containers)", - "container": "Container", - "container.placeholder": "Drag and drop a container or enter HTML here.", - "show-to-groups": "Show to groups", - "hide-from-groups": "Hide from groups", - "hide-on-mobile": "Hide on mobile" + "title": "標題", + "title.placeholder": "標題(僅在部分容器顯示)", + "container": "容器", + "container.placeholder": "將容器拖拽至此處或在此處輸入HTML", + "show-to-groups": "對群組顯示", + "hide-from-groups": "對群組隱藏", + "hide-on-mobile": "在移動端隱藏" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/general/dashboard.json b/public/language/zh-TW/admin/general/dashboard.json index ee1f8c0779..82b1238d28 100644 --- a/public/language/zh-TW/admin/general/dashboard.json +++ b/public/language/zh-TW/admin/general/dashboard.json @@ -1,79 +1,79 @@ { - "forum-traffic": "Forum Traffic", - "page-views": "Page Views", - "unique-visitors": "Unique Visitors", - "new-users": "新的使用者", - "posts": "Posts", - "topics": "Topics", - "page-views-seven": "Last 7 Days", - "page-views-thirty": "Last 30 Days", - "page-views-last-day": "Last 24 hours", - "page-views-custom": "Custom Date Range", - "page-views-custom-start": "Range Start", - "page-views-custom-end": "Range End", - "page-views-custom-help": "Enter a date range of page views you would like to view. If no date picker is available, the accepted format is YYYY-MM-DD", - "page-views-custom-error": "Please enter a valid date range in the format YYYY-MM-DD", + "forum-traffic": "論壇流量", + "page-views": "頁面瀏覽量", + "unique-visitors": "不重複訪客", + "new-users": "新使用者", + "posts": "貼文", + "topics": "主題", + "page-views-seven": "最近7天", + "page-views-thirty": "最近30天", + "page-views-last-day": "最近24小時", + "page-views-custom": "自定義日期範圍", + "page-views-custom-start": "範圍開始", + "page-views-custom-end": "範圍結束", + "page-views-custom-help": "輸入您要查看的網頁瀏覽日期範圍。 如果沒有日期選擇器可用,則接受的格式是 YYYY-MM-DD", + "page-views-custom-error": "請輸入 YYYY-MM-DD格式的有效日期範圍 ", - "stats.yesterday": "Yesterday", - "stats.today": "Today", - "stats.last-week": "Last Week", - "stats.this-week": "This Week", - "stats.last-month": "Last Month", - "stats.this-month": "This Month", - "stats.all": "All Time", + "stats.yesterday": "昨天", + "stats.today": "今天", + "stats.last-week": "上一週", + "stats.this-week": "本週", + "stats.last-month": "上一月", + "stats.this-month": "本月", + "stats.all": "總計", - "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.", - "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.", - "latest-lookup-failed": "

Failed to look up latest available version of NodeBB

", + "updates": "更新", + "running-version": "您正在運行 NodeBB v%1 .", + "keep-updated": "請確保您已及時更新 NodeBB 以獲得最新的安全修補程式與 Bug 修復。", + "up-to-date": "

正在使用 最新版本

", + "upgrade-available": "

新的版本 (v%1) 已經發布。建議您 升級 NodeBB

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

這是一個已經過期的預發佈版本的 NodeBB,新的版本 (v%1) 已經發布。建議您 升級 NodeBB

", + "prerelease-warning": "

正在使用測試版 NodeBB。可能會出現意外的 Bug。

", + "running-in-development": "論壇正處於開發模式,這可能使其暴露於潛在的危險之中;請聯繫您的系統管理員。", + "latest-lookup-failed": "

無法找到 NodeBB 的最新可用版本

", - "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": "提醒", + "restart-not-required": "不需要重啟", + "restart-required": "需要重啟", + "search-plugin-installed": "已安裝搜尋外掛", + "search-plugin-not-installed": "未安裝搜尋外掛", + "search-plugin-tooltip": "在外掛頁面安裝搜尋外掛來啟用搜尋功能", - "control-panel": "System Control", - "rebuild-and-restart": "Rebuild & Restart", - "restart": "Restart", - "restart-warning": "Rebuilding or Restarting your NodeBB will drop all existing connections for a few seconds.", - "restart-disabled": "Rebuilding and Restarting your NodeBB has been disabled as you do not seem to be running it via the appropriate daemon.", - "maintenance-mode": "Maintenance Mode", - "maintenance-mode-title": "Click here to set up maintenance mode for NodeBB", - "realtime-chart-updates": "Realtime Chart Updates", + "control-panel": "系統控制", + "rebuild-and-restart": "重建 & 重啟", + "restart": "重啟", + "restart-warning": "重載或重啟 NodeBB 會丟失數秒內全部的連接。", + "restart-disabled": "重建和重新啟動NodeBB已被禁用,因為您似乎沒有通過適當的守護進程運行它。", + "maintenance-mode": "維護模式", + "maintenance-mode-title": "點擊此處設置 NodeBB 的維護模式", + "realtime-chart-updates": "即時圖表更新", - "active-users": "Active Users", - "active-users.users": "Users", - "active-users.guests": "Guests", - "active-users.total": "Total", - "active-users.connections": "Connections", + "active-users": "活躍使用者", + "active-users.users": "使用者", + "active-users.guests": "訪客", + "active-users.total": "全部", + "active-users.connections": "連線", - "anonymous-registered-users": "Anonymous vs Registered Users", - "anonymous": "Anonymous", - "registered": "Registered", + "anonymous-registered-users": "匿名 vs 註冊使用者", + "anonymous": "匿名", + "registered": "已註冊", - "user-presence": "User Presence", - "on-categories": "On categories list", - "reading-posts": "Reading posts", - "browsing-topics": "Browsing topics", - "recent": "Recent", - "unread": "Unread", + "user-presence": "使用者光臨", + "on-categories": "在版面列表", + "reading-posts": "閱讀貼文", + "browsing-topics": "瀏覽主題", + "recent": "最近", + "unread": "未讀", - "high-presence-topics": "High Presence Topics", + "high-presence-topics": "熱門主題", - "graphs.page-views": "Page Views", - "graphs.page-views-registered": "Page Views Registered", - "graphs.page-views-guest": "Page Views Guest", - "graphs.page-views-bot": "Page Views Bot", - "graphs.unique-visitors": "Unique Visitors", - "graphs.registered-users": "Registered Users", - "graphs.anonymous-users": "Anonymous Users", - "last-restarted-by": "Last restarted by", - "no-users-browsing": "No users browsing" + "graphs.page-views": "頁面瀏覽量", + "graphs.page-views-registered": "註冊使用者頁面瀏覽量", + "graphs.page-views-guest": "訪客頁面瀏覽量", + "graphs.page-views-bot": "爬蟲頁面瀏覽量", + "graphs.unique-visitors": "不重複訪客", + "graphs.registered-users": "已註冊使用者", + "graphs.anonymous-users": "匿名使用者", + "last-restarted-by": "上次重啟管理員/時間", + "no-users-browsing": "沒有使用者正在瀏覽" } diff --git a/public/language/zh-TW/admin/general/homepage.json b/public/language/zh-TW/admin/general/homepage.json index 7428d59eeb..28e579ad56 100644 --- a/public/language/zh-TW/admin/general/homepage.json +++ b/public/language/zh-TW/admin/general/homepage.json @@ -1,8 +1,8 @@ { - "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", - "custom-route": "Custom Route", - "allow-user-home-pages": "Allow User Home Pages", - "home-page-title": "Title of the home page (default \"Home\")" + "home-page": "首頁", + "description": "請選擇使用者到達根 URL 時所顯示的頁面。", + "home-page-route": "首頁路徑", + "custom-route": "自訂路徑", + "allow-user-home-pages": "允許使用者自訂首頁", + "home-page-title": "首頁標題(預設為“Home”)" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/general/languages.json b/public/language/zh-TW/admin/general/languages.json index de8ef3e3a4..c8f7db09de 100644 --- a/public/language/zh-TW/admin/general/languages.json +++ b/public/language/zh-TW/admin/general/languages.json @@ -1,6 +1,6 @@ { "language-settings": "語言設定", - "description": "所有使用者的預設語言,使用者可以在個人設定內修改", + "description": "預設語言會決定所有使用者的語言設定。
單一使用者可以各自在帳戶設定中覆蓋此項設定。", "default-language": "預設語言", - "auto-detect": "使用者為訪客時,自動偵測語言設定" + "auto-detect": "自動檢測訪客的語言設定" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/general/navigation.json b/public/language/zh-TW/admin/general/navigation.json index 13dd01aae7..15ac71b9a0 100644 --- a/public/language/zh-TW/admin/general/navigation.json +++ b/public/language/zh-TW/admin/general/navigation.json @@ -1,23 +1,23 @@ { - "icon": "Icon:", - "change-icon": "change", - "route": "Route:", - "tooltip": "Tooltip:", - "text": "Text:", - "text-class": "Text Class: optional", - "class": "Class: optional", - "id": "ID: optional", + "icon": "圖示:", + "change-icon": "更改", + "route": "路徑:", + "tooltip": "提示:", + "text": "文字:", + "text-class": "文字類別:可選", + "class": "類: 可選", + "id": "ID:可選", - "properties": "Properties:", - "groups": "Groups:", - "open-new-window": "Open in a new window", + "properties": "屬性:", + "groups": "群組:", + "open-new-window": "在新窗口中打開", - "btn.delete": "Delete", - "btn.disable": "Disable", - "btn.enable": "Enable", + "btn.delete": "刪除", + "btn.disable": "禁用", + "btn.enable": "啟用", - "available-menu-items": "Available Menu Items", - "custom-route": "Custom Route", - "core": "core", - "plugin": "plugin" + "available-menu-items": "可用的選單項目", + "custom-route": "自訂路徑", + "core": "核心", + "plugin": "外掛" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/general/social.json b/public/language/zh-TW/admin/general/social.json index 23aedfcfaa..aa16e3b8f5 100644 --- a/public/language/zh-TW/admin/general/social.json +++ b/public/language/zh-TW/admin/general/social.json @@ -1,5 +1,5 @@ { - "post-sharing": "Post Sharing", - "info-plugins-additional": "Plugins can add additional networks for sharing posts.", - "save-success": "Successfully saved Post Sharing Networks!" + "post-sharing": "貼文分享", + "info-plugins-additional": "外掛可以增加額外用於分享貼文的社群媒體。", + "save-success": "已成功儲存貼文分享社群媒體。" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/general/sounds.json b/public/language/zh-TW/admin/general/sounds.json index 95ccbde0f1..e30202df06 100644 --- a/public/language/zh-TW/admin/general/sounds.json +++ b/public/language/zh-TW/admin/general/sounds.json @@ -1,9 +1,9 @@ { - "notifications": "Notifications", - "chat-messages": "Chat Messages", - "play-sound": "Play", - "incoming-message": "Incoming Message", - "outgoing-message": "Outgoing Message", - "upload-new-sound": "Upload New Sound", - "saved": "Settings Saved" + "notifications": "通知", + "chat-messages": "聊天訊息", + "play-sound": "播放", + "incoming-message": "收到的訊息", + "outgoing-message": "發出的訊息", + "upload-new-sound": "上傳新的音檔", + "saved": "設定已儲存" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/admins-mods.json b/public/language/zh-TW/admin/manage/admins-mods.json index e0f39ed5d4..172480b248 100644 --- a/public/language/zh-TW/admin/manage/admins-mods.json +++ b/public/language/zh-TW/admin/manage/admins-mods.json @@ -1,10 +1,10 @@ { - "administrators": "Administrators", - "global-moderators": "Global Moderators", - "no-global-moderators": "No Global Moderators", - "moderators-of-category": "%1 Moderators", - "no-moderators": "No Moderators", - "add-administrator": "Add Administrator", - "add-global-moderator": "Add Global Moderator", - "add-moderator": "Add Moderator" + "administrators": "管理員", + "global-moderators": "超級版主", + "no-global-moderators": "沒有超級版主", + "moderators-of-category": "%1 版主", + "no-moderators": "沒有版主", + "add-administrator": "添加管理員", + "add-global-moderator": "新增超級版主", + "add-moderator": "新增版主" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/categories.json b/public/language/zh-TW/admin/manage/categories.json index 1eda6ea080..8d40c98725 100644 --- a/public/language/zh-TW/admin/manage/categories.json +++ b/public/language/zh-TW/admin/manage/categories.json @@ -1,81 +1,81 @@ { - "settings": "Category Settings", - "privileges": "Privileges", + "settings": "版面設定", + "privileges": "權限", - "name": "Category Name", - "description": "Category Description", - "bg-color": "Background Colour", - "text-color": "Text Colour", - "bg-image-size": "Background Image Size", - "custom-class": "Custom Class", - "num-recent-replies": "# of Recent Replies", - "ext-link": "External Link", - "is-section": "Treat this category as a section", - "upload-image": "Upload Image", - "delete-image": "Remove", - "category-image": "Category Image", - "parent-category": "Parent Category", - "optional-parent-category": "(Optional) Parent Category", - "parent-category-none": "(None)", - "copy-parent": "Copy Parent", - "copy-settings": "Copy Settings From", - "optional-clone-settings": "(Optional) Clone Settings From Category", - "clone-children": "Clone Children Categories And Settings", - "purge": "Purge Category", + "name": "版面名稱", + "description": "版面描述", + "bg-color": "背景顏色", + "text-color": "圖示顏色", + "bg-image-size": "背景圖片大小", + "custom-class": "自訂 Class", + "num-recent-replies": "最近回覆數", + "ext-link": "外部連結", + "is-section": "將該版面作為分段", + "upload-image": "上傳圖片", + "delete-image": "移除", + "category-image": "版面圖片", + "parent-category": "上層版面", + "optional-parent-category": "(可選)上層版面", + "parent-category-none": "(無)", + "copy-parent": "複製 上層版面", + "copy-settings": "複製設定", + "optional-clone-settings": "(可選) 從版面複製設定", + "clone-children": "複製子版面並進行設定", + "purge": "刪除版面", - "enable": "Enable", - "disable": "Disable", - "edit": "Edit", + "enable": "啟用", + "disable": "禁用", + "edit": "編輯", - "select-category": "Select Category", - "set-parent-category": "Set Parent Category", + "select-category": "選擇版面", + "set-parent-category": "設置上層版面", - "privileges.description": "You can configure the access control privileges for portions of the site in this section. Privileges can be granted on a per-user or a per-group basis. Select the domain of effect from the dropdown below.", - "privileges.category-selector": "Configuring privileges for ", - "privileges.warning": "Note: Privilege settings take effect immediately. It is not necessary to save the category after adjusting these settings.", - "privileges.section-viewing": "Viewing Privileges", - "privileges.section-posting": "Posting Privileges", - "privileges.section-moderation": "Moderation Privileges", - "privileges.section-other": "Other", - "privileges.section-user": "User", - "privileges.search-user": "Add User", - "privileges.no-users": "No user-specific privileges in this category.", - "privileges.section-group": "Group", - "privileges.group-private": "This group is private", - "privileges.search-group": "Add Group", - "privileges.copy-to-children": "Copy to Children", - "privileges.copy-from-category": "Copy from Category", - "privileges.copy-privileges-to-all-categories": "Copy to All Categories", - "privileges.copy-group-privileges-to-children": "Copy this group's privileges to the children of this category.", - "privileges.copy-group-privileges-to-all-categories": "Copy this group's privileges to all categories.", - "privileges.copy-group-privileges-from": "Copy this group's privileges from another category.", - "privileges.inherit": "If the registered-users group is granted a specific privilege, all other groups receive an implicit privilege, even if they are not explicitly defined/checked. This implicit privilege is shown to you because all users are part of the registered-users user group, and so, privileges for additional groups need not be explicitly granted.", - "privileges.copy-success": "Privileges copied!", + "privileges.description": "您可以在此面板中為網站的某些部分設定訪問控制權。可以分別為每個使用者或每個使用者群組授予權限。從下方的下拉列表中選擇作用的網域。", + "privileges.category-selector": "為該版面設定權限:", + "privileges.warning": "注意:權限設定會立即生效。 調整這些設定後,無需保存。", + "privileges.section-viewing": "查看權限", + "privileges.section-posting": "發文權限", + "privileges.section-moderation": "審核權限", + "privileges.section-other": "其它", + "privileges.section-user": "使用者", + "privileges.search-user": "新增使用者", + "privileges.no-users": "此版面中沒有使用者特定的權限。", + "privileges.section-group": "群組", + "privileges.group-private": "這個群組是私密的", + "privileges.search-group": "新增群組", + "privileges.copy-to-children": "複製到子版面", + "privileges.copy-from-category": "從版面複製", + "privileges.copy-privileges-to-all-categories": "複製到全部版面", + "privileges.copy-group-privileges-to-children": "複製此使用者群組的權限到此版面的下層", + "privileges.copy-group-privileges-to-all-categories": "複製此使用者群組的權限到全部版面", + "privileges.copy-group-privileges-from": "從其它版面複製權限到此使用者群組", + "privileges.inherit": "如果 registered-users 群組被授予特定權限,所有其他群組都會收到隱式權限,即使它們未被明確定義/勾選。 將顯示此隱式權限,因為所有使用者都是 registered-users 群組的一部分,因此無需顯式授予其它群組的權限。", + "privileges.copy-success": "權限已複製", - "analytics.back": "Back to Categories List", - "analytics.title": "Analytics for \"%1\" category", - "analytics.pageviews-hourly": "Figure 1 – Hourly page views for this category", - "analytics.pageviews-daily": "Figure 2 – Daily page views for this category", - "analytics.topics-daily": "Figure 3 – Daily topics created in this category", - "analytics.posts-daily": "Figure 4 – Daily posts made in this category", + "analytics.back": "返回版面列表", + "analytics.title": "“%1”版面的統計", + "analytics.pageviews-hourly": "圖1 – 此版面的每小時頁面瀏覽量", + "analytics.pageviews-daily": "圖2 – 此版面的每日頁面瀏覽量", + "analytics.topics-daily": "圖3 – 每日在此版面中建立的主題", + "analytics.posts-daily": "圖4 – 每日在此版面中每日發佈的貼文", - "alert.created": "Created", - "alert.create-success": "Category successfully created!", - "alert.none-active": "You have no active categories.", - "alert.create": "Create a Category", - "alert.confirm-moderate": "Are you sure you wish to grant the moderation privilege to this user group? This group is public, and any users can join at will.", - "alert.confirm-purge": "

Do you really want to purge this category \"%1\"?

Warning! All topics and posts in this category will be purged!

Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.

", - "alert.purge-success": "Category purged!", - "alert.copy-success": "Settings Copied!", - "alert.set-parent-category": "Set Parent Category", - "alert.updated": "Updated Categories", - "alert.updated-success": "Category IDs %1 successfully updated.", - "alert.upload-image": "Upload category image", - "alert.find-user": "Find a User", - "alert.user-search": "Search for a user here...", - "alert.find-group": "Find a Group", - "alert.group-search": "Search for a group here...", - "collapse-all": "Collapse All", - "expand-all": "Expand All", - "disable-on-create": "Disable on create" + "alert.created": "建立", + "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.find-user": "尋找使用者", + "alert.user-search": "在這裡尋找使用者…", + "alert.find-group": "尋找群組", + "alert.group-search": "在此處搜尋群組...", + "collapse-all": "全部摺疊", + "expand-all": "全部展開", + "disable-on-create": "建立後禁用" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/digest.json b/public/language/zh-TW/admin/manage/digest.json index 075b8e1aff..6dea54506f 100644 --- a/public/language/zh-TW/admin/manage/digest.json +++ b/public/language/zh-TW/admin/manage/digest.json @@ -1,21 +1,21 @@ { - "lead": "A listing of digest delivery stats and times is displayed below.", - "disclaimer": "Please be advised that email delivery is not guaranteed, due to the nature of email technology. Many variables factor into whether an email sent to the recipient server is ultimately delivered into the user's inbox, including server reputation, blacklisted IP addresses, and whether DKIM/SPF/DMARC is configured.", - "disclaimer-continued": "A successful delivery means the message was sent successfully by NodeBB and acknowledged by the recipient server. It does not mean the email landed in the inbox. For best results, we recommend using a third-party email delivery service such as SendGrid.", + "lead": "以下是摘要發送狀態及時間列表", + "disclaimer": "請注意,由於 Email 技術本身的原因,郵件不一定能保證送達。有很多因素都會導致郵件無法到達使用者的信箱,比如郵件伺服器的信譽、IP 地址黑名單、DNS 的 DKIM/SPF/DMARC 設定等。", + "disclaimer-continued": "成功發送意味訊息被 NodeBB 成功發送且被接收人伺服器收到。但這並不等同於郵件發送到了信箱箱中。為了確保信息可以準確送達,我們建議使用第三方的郵件服務,例如SendGrid。", - "user": "User", - "subscription": "Subscription Type", - "last-delivery": "Last successful delivery", - "default": "System default", - "default-help": "System default means the user has not explicitly overridden the global forum setting for digests, which is currently: "%1"", - "resend": "Resend Digest", - "resend-all-confirm": "Are you sure you wish to mnually execute this digest run?", - "resent-single": "Manual digest resend completed", - "resent-day": "Daily digest resent", - "resent-week": "Weekly digest resent", - "resent-month": "Monthly digest resent", - "null": "Never", - "manual-run": "Manual digest run:", + "user": "使用者", + "subscription": "訂閱類型", + "last-delivery": "上次成功通知", + "default": "系統預設", + "default-help": "System default 表示使用者尚未明確覆寫摘要的全域論壇設置,該設定當前為: “%1“", + "resend": "重發摘要", + "resend-all-confirm": "您確認要手動執行這個摘要嗎?", + "resent-single": "摘要重發操作完成", + "resent-day": "已發送每日摘要", + "resent-week": "已發送每週摘要", + "resent-month": "已發送每月摘要", + "null": "從不", + "manual-run": "手動執行摘要:", - "no-delivery-data": "No delivery data found" + "no-delivery-data": "找不到發送資料" } \ 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 c3d60d4eed..c17ced75a3 100644 --- a/public/language/zh-TW/admin/manage/groups.json +++ b/public/language/zh-TW/admin/manage/groups.json @@ -1,41 +1,41 @@ { - "name": "Group Name", - "badge": "Badge", - "properties": "Properties", - "description": "Group Description", - "member-count": "Member Count", - "system": "System", - "hidden": "Hidden", - "private": "Private", - "edit": "Edit", - "search-placeholder": "Search", - "create": "Create Group", - "description-placeholder": "A short description about your group", - "create-button": "Create", + "name": "群組名", + "badge": "獎章", + "properties": "屬性", + "description": "群組描述", + "member-count": "成員數量", + "system": "系統", + "hidden": "隱藏", + "private": "私有", + "edit": "編輯", + "search-placeholder": "搜尋", + "create": "建立群組", + "description-placeholder": "一個關於你的群組的簡短描述", + "create-button": "建立", - "alerts.create-failure": "Uh-Oh

There was a problem creating your group. Please try again later!

", - "alerts.confirm-delete": "Are you sure you wish to delete this group?", + "alerts.create-failure": "哦不!

建立您的群組時出現問題。 請稍後再試!

", + "alerts.confirm-delete": "您確定要刪除此群組嗎?", - "edit.name": "Name", - "edit.description": "Description", - "edit.user-title": "Title of Members", - "edit.icon": "Group Icon", - "edit.label-color": "Group Label Color", - "edit.text-color": "Group Text Color", - "edit.show-badge": "Show Badge", - "edit.private-details": "If enabled, joining of groups requires approval from a group owner.", - "edit.private-override": "Warning: Private groups is disabled at system level, which overrides this option.", - "edit.disable-join": "Disable join requests", - "edit.disable-leave": "Disallow users from leaving the group", - "edit.hidden": "Hidden", - "edit.hidden-details": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually", - "edit.add-user": "Add User to Group", - "edit.add-user-search": "Search Users", - "edit.members": "Member List", - "control-panel": "Groups Control Panel", - "revert": "Revert", + "edit.name": "名稱", + "edit.description": "描述", + "edit.user-title": "成員標題", + "edit.icon": "群組圖示", + "edit.label-color": "群組標籤顏色", + "edit.text-color": "群組文字顏色", + "edit.show-badge": "顯示徽章", + "edit.private-details": "啟用此選項後,加入群組的請求將需要群組所有者審核。", + "edit.private-override": "警告:系統已禁用了私有群組,優先性高於該選項。", + "edit.disable-join": "禁止申請加入群組", + "edit.disable-leave": "禁止使用者離開群組", + "edit.hidden": "隱藏", + "edit.hidden-details": "啟用此選項後,此群組將不在群組列表呈現,並且使用者只能被手動邀請加入", + "edit.add-user": "向此群組新增成員", + "edit.add-user-search": "搜尋使用者", + "edit.members": "成員列表", + "control-panel": "群組控制面板", + "revert": "重置", - "edit.no-users-found": "No Users Found", - "edit.confirm-remove-user": "Are you sure you want to remove this user?", - "edit.save-success": "Changes saved!" + "edit.no-users-found": "沒有找到使用者", + "edit.confirm-remove-user": "確認刪除此使用者嗎?", + "edit.save-success": "設定已儲存!" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/ip-blacklist.json b/public/language/zh-TW/admin/manage/ip-blacklist.json index 588fbd62b6..b98811a04c 100644 --- a/public/language/zh-TW/admin/manage/ip-blacklist.json +++ b/public/language/zh-TW/admin/manage/ip-blacklist.json @@ -1,19 +1,19 @@ { - "lead": "Configure your IP blacklist here.", - "description": "Occasionally, a user account ban is not enough of a deterrant. Other times, restricting access to the forum to a specific IP or a range of IPs is the best way to protect a forum. In these scenarios, you can add troublesome IP addresses or entire CIDR blocks to this blacklist, and they will be prevented from logging in to or registering a new account.", - "active-rules": "Active Rules", - "validate": "Validate Blacklist", - "apply": "Apply Blacklist", - "hints": "Syntax Hints", - "hint-1": "Define a single IP addresses per line. You can add IP blocks as long as they follow the CIDR format (e.g. 192.168.100.0/22).", - "hint-2": "You can add in comments by starting lines with the # symbol.", + "lead": "在此設定 IP 黑名單", + "description": "有時,一份帳戶封鎖並不足以作為威懾。更多的時候,限制有權瀏覽論壇的具體 IP 或者一個 IP 範圍這一行為可以更有效地保護論壇。在以上情況下,您可以新增一些令人厭惡者的 IP 地址或者 CIDR 地址塊到此黑名單,此後他們(被加入黑名單者)將被阻止進行登入或者註冊新帳戶的行為。", + "active-rules": "生效規則", + "validate": "驗證黑名單", + "apply": "應用黑名單", + "hints": "格式建議", + "hint-1": "每行定義一個獨立 IP 地址。您可以添加 IP 範圍,只要它們滿足 CIDR 格式(e.g. 192.168.100.0/22)。", + "hint-2": "您可以通過以#標誌開頭的行來添加註解。", - "validate.x-valid": "%1 out of %2 rule(s) valid.", - "validate.x-invalid": "The following %1 rules are invalid:", + "validate.x-valid": "%1 / %2的規則有效。", + "validate.x-invalid": "下列 %0 個規則無效:", - "alerts.applied-success": "Blacklist Applied", + "alerts.applied-success": "黑名單生效", - "analytics.blacklist-hourly": "Figure 1 – Blacklist hits per hour", - "analytics.blacklist-daily": "Figure 2 – Blacklist hits per day", - "ip-banned": "IP banned" + "analytics.blacklist-hourly": "圖 1 – 每小時觸發黑名單數", + "analytics.blacklist-daily": "圖 2– 每日觸發黑名單數", + "ip-banned": "已封鎖IP" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/post-queue.json b/public/language/zh-TW/admin/manage/post-queue.json index 2742e3a7af..9907d7d81a 100644 --- a/public/language/zh-TW/admin/manage/post-queue.json +++ b/public/language/zh-TW/admin/manage/post-queue.json @@ -1,11 +1,11 @@ { - "post-queue": "Post Queue", - "description": "There are no posts in the post queue.
To enable this feature, go to Settings → Post → Post Queue and enable Post Queue.", - "user": "User", - "category": "Category", - "title": "Title", - "content": "Content", - "posted": "Posted", - "reply-to": "Reply to \"%1\"", - "content-editable": "You can click on individual content to edit before posting." + "post-queue": "貼文隊列", + "description": "貼文隊列中暫無新貼文。啟用此功能,請前往設定→貼文→貼文隊列,然後啟用貼文隊列。", + "user": "使用者", + "category": "版面", + "title": "標題", + "content": "內容", + "posted": "發佈", + "reply-to": "回覆\"%1\"", + "content-editable": "發文前,您可以點擊內容進行編輯。" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/privileges.json b/public/language/zh-TW/admin/manage/privileges.json index faabb1a7b4..e26829b5f8 100644 --- a/public/language/zh-TW/admin/manage/privileges.json +++ b/public/language/zh-TW/admin/manage/privileges.json @@ -1,35 +1,35 @@ { - "global": "Global", - "global.no-users": "No user-specific global privileges.", - "group-privileges": "Group Privileges", - "user-privileges": "User Privileges", - "chat": "Chat", - "upload-images": "Upload Images", - "upload-files": "Upload Files", - "signature": "Signature", - "ban": "Ban", - "search-content": "Search Content", - "search-users": "Search Users", - "search-tags": "Search Tags", - "view-users": "View Users", - "view-tags": "View Tags", - "view-groups": "View Groups", - "allow-local-login": "Local Login", - "allow-group-creation": "Group Create", - "view-users-info": "View Users Info", - "find-category": "Find Category", - "access-category": "Access Category", - "access-topics": "Access Topics", - "create-topics": "Create Topics", - "reply-to-topics": "Reply to Topics", - "tag-topics": "Tag Topics", - "edit-posts": "Edit Posts", - "view-edit-history": "View Edit History", - "delete-posts": "Delete Posts", - "view_deleted": "View Deleted Posts", - "upvote-posts": "Upvote Posts", - "downvote-posts": "Downvote Posts", - "delete-topics": "Delete Topics", - "purge": "Purge", - "moderate": "Moderate" + "global": "全域", + "global.no-users": "沒有使用者專用的全域權限。", + "group-privileges": "群組權限", + "user-privileges": "使用者權限", + "chat": "聊天", + "upload-images": "上傳圖片", + "upload-files": "上傳檔案", + "signature": "簽名檔", + "ban": "禁用", + "search-content": "搜尋內容", + "search-users": "搜尋使用者", + "search-tags": "搜尋標籤", + "view-users": "瀏覽使用者", + "view-tags": "瀏覽標籤", + "view-groups": "瀏覽群組", + "allow-local-login": "本地登入", + "allow-group-creation": "群組建立", + "view-users-info": "檢視使用者資訊", + "find-category": "尋找版面", + "access-category": "存取版面", + "access-topics": "存取主題", + "create-topics": "建立主題", + "reply-to-topics": "回覆主題", + "tag-topics": "新增標籤", + "edit-posts": "修改回覆", + "view-edit-history": "查看變更歷史", + "delete-posts": "刪除回覆", + "view_deleted": "查看已刪除回覆", + "upvote-posts": "點贊", + "downvote-posts": "倒讚", + "delete-topics": "刪除主題", + "purge": "清除", + "moderate": "編審" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/registration.json b/public/language/zh-TW/admin/manage/registration.json index f51b4d56e6..1ef5a5f23e 100644 --- a/public/language/zh-TW/admin/manage/registration.json +++ b/public/language/zh-TW/admin/manage/registration.json @@ -1,20 +1,20 @@ { - "queue": "Queue", - "description": "There are no users in the registration queue.
To enable this feature, go to Settings → User → User Registration and set Registration Type to \"Admin Approval\".", + "queue": "申請", + "description": "註冊申請佇列裡面還沒有使用者申請。
要開啟這項功能,請去設定 → 使用者 → 使用者註冊 並設定註冊類型為“管理員批准”。", - "list.name": "Name", - "list.email": "Email", + "list.name": "姓名", + "list.email": "郵件", "list.ip": "IP", - "list.time": "Time", - "list.username-spam": "Frequency: %1 Appears: %2 Confidence: %3", - "list.email-spam": "Frequency: %1 Appears: %2", - "list.ip-spam": "Frequency: %1 Appears: %2", + "list.time": "時間", + "list.username-spam": "頻率: %1 顯示:%2 信心:%3", + "list.email-spam": "頻率:%1 顯示: %2", + "list.ip-spam": "頻率:%1 顯示: %2", - "invitations": "Invitations", - "invitations.description": "Below is a complete list of invitations sent. Use ctrl-f to search through the list by email or username.

The username will be displayed to the right of the emails for users who have redeemed their invitations.", - "invitations.inviter-username": "Inviter Username", - "invitations.invitee-email": "Invitee Email", - "invitations.invitee-username": "Invitee Username (if registered)", + "invitations": "邀請", + "invitations.description": "下面列出了所有已發送的邀請。您可以使用 Ctrl+F 快捷鍵搜索列表中的郵箱地址或帳戶。

如果使用者接受了邀請,他的帳戶將會被顯示在郵箱右邊。", + "invitations.inviter-username": "邀請人帳戶", + "invitations.invitee-email": "受邀請的電子信箱", + "invitations.invitee-username": "受邀請的帳戶(如果已經註冊)", - "invitations.confirm-delete": "Are you sure you wish to delete this invitation?" + "invitations.confirm-delete": "確認刪除這個邀請?" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/tags.json b/public/language/zh-TW/admin/manage/tags.json index df597a6166..d487e25855 100644 --- a/public/language/zh-TW/admin/manage/tags.json +++ b/public/language/zh-TW/admin/manage/tags.json @@ -1,19 +1,18 @@ { - "none": "Your forum does not have any topics with tags yet.", - "bg-color": "Background Colour", - "text-color": "Text Colour", - "create-modify": "Create & Modify Tags", - "description": "Select tags via clicking and/or dragging, use shift to select multiple.", - "create": "Create Tag", - "modify": "Modify Tags", - "rename": "Rename Tags", - "delete": "Delete Selected Tags", - "search": "Search for tags...", - "settings": "Click here to visit the tag settings page.", - "name": "Tag Name", + "none": "您的論壇目前沒有帶有標籤的主題。", + "bg-color": "背景顏色", + "text-color": "文字顏色", + "create-modify": "建立或修改標籤", + "description": "通過點擊或拖曳選擇標籤,按住shift進行多選。", + "create": "建立標籤", + "modify": "修改標籤", + "rename": "重命名標籤", + "delete": "刪除所選標籤", + "search": "搜尋標籤...", + "settings": "點擊此處 訪問標籤設定頁面。", + "name": "標籤名", - "alerts.editing-multiple": "Editing multiple tags", - "alerts.editing-x": "Editing \"%1\" tag", - "alerts.confirm-delete": "Do you want to delete the selected tags?", - "alerts.update-success": "Tag Updated!" + "alerts.editing": "編輯標籤", + "alerts.confirm-delete": "是否要刪除所選標籤?", + "alerts.update-success": "標籤已更新!" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/uploads.json b/public/language/zh-TW/admin/manage/uploads.json index 21bc8201fc..3e4889509e 100644 --- a/public/language/zh-TW/admin/manage/uploads.json +++ b/public/language/zh-TW/admin/manage/uploads.json @@ -1,9 +1,9 @@ { - "upload-file": "Upload File", - "filename": "Filename", - "usage": "Post Usage", - "orphaned": "Orphaned", - "size/filecount": "Size / Filecount", - "confirm-delete": "Do you really want to delete this file?", - "filecount": "%1 files" + "upload-file": "上傳檔案", + "filename": "檔案名", + "usage": "使用的貼文", + "orphaned": "未使用", + "size/filecount": "大小/檔案數", + "confirm-delete": "您確定要刪除此檔案嗎?", + "filecount": "%1 個檔案" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/manage/users.json b/public/language/zh-TW/admin/manage/users.json index 63b9d7463a..d251577eb4 100644 --- a/public/language/zh-TW/admin/manage/users.json +++ b/public/language/zh-TW/admin/manage/users.json @@ -1,109 +1,109 @@ { - "users": "用戶", + "users": "使用者", "edit": "編輯", - "make-admin": "授予管理員", - "remove-admin": "移除管理員", - "validate-email": "驗證電子郵箱", - "send-validation-email": "發送驗證電子郵件", - "password-reset-email": "發送重置電子郵件", - "force-password-reset": "Force Password Reset & Log User Out", - "ban": "封鎖用戶", - "temp-ban": "臨時封鎖用戶", - "unban": "解禁用戶", - "reset-lockout": "Reset Lockout", - "reset-flags": "Reset Flags", - "delete": "Delete User(s)", - "purge": "Delete User(s) and Content", - "download-csv": "下載 CSV 檔案", - "manage-groups": "Manage Groups", - "add-group": "Add Group", + "make-admin": "設為管理員", + "remove-admin": "撤銷管理員", + "validate-email": "驗證電郵地址", + "send-validation-email": "發送驗證郵件", + "password-reset-email": "發送密碼重設郵件", + "force-password-reset": "強制密碼重設 & 登入使用者已退出", + "ban": "封鎖使用者", + "temp-ban": "暫時封鎖使用者", + "unban": "解封使用者", + "reset-lockout": "重設封鎖", + "reset-flags": "重設舉報", + "delete": "刪除使用者", + "purge": "刪除使用者和內容", + "download-csv": "下載CSV", + "manage-groups": "管理群組", + "add-group": "新增至群組", "invite": "邀請", - "new": "New User", + "new": "新建使用者", - "pills.latest": "Latest Users", - "pills.unvalidated": "Not Validated", - "pills.no-posts": "No Posts", - "pills.top-posters": "Top Posters", - "pills.top-rep": "Most Reputation", - "pills.inactive": "Inactive", - "pills.flagged": "Most Flagged", - "pills.banned": "Banned", - "pills.search": "User Search", + "pills.latest": "最近的使用者", + "pills.unvalidated": "未驗證", + "pills.no-posts": "沒有貼文", + "pills.top-posters": "發文最多", + "pills.top-rep": "聲望最高", + "pills.inactive": "不活躍", + "pills.flagged": "舉報最多", + "pills.banned": "被封鎖", + "pills.search": "搜尋使用者", - "50-per-page": "50 per page", - "100-per-page": "100 per page", - "250-per-page": "250 per page", - "500-per-page": "500 per page", + "50-per-page": "每頁50", + "100-per-page": "每頁100", + "250-per-page": "每頁250", + "500-per-page": "每頁500", - "search.uid": "By User ID", - "search.uid-placeholder": "Enter a user ID to search", - "search.username": "By User Name", - "search.username-placeholder": "Enter a username to search", - "search.email": "By Email", - "search.email-placeholder": "Enter a email to search", - "search.ip": "By IP Address", - "search.ip-placeholder": "Enter an IP Address to search", - "search.not-found": "User not found!", + "search.uid": "通過使用者ID", + "search.uid-placeholder": "搜尋使用者ID", + "search.username": "通過使用者名", + "search.username-placeholder": "輸入您想查詢的使用者名", + "search.email": "通過電郵地址", + "search.email-placeholder": "輸入您想查詢的電郵地址", + "search.ip": "通過IP地址", + "search.ip-placeholder": "輸入您想查詢的 IP 地址", + "search.not-found": "未找到使用者!", - "inactive.3-months": "3 months", - "inactive.6-months": "6 months", - "inactive.12-months": "12 months", + "inactive.3-months": "3個月", + "inactive.6-months": "6個月", + "inactive.12-months": "12個月", - "users.uid": "uid", - "users.username": "username", - "users.email": "email", - "users.postcount": "postcount", - "users.reputation": "reputation", - "users.flags": "flags", - "users.joined": "joined", - "users.last-online": "last online", - "users.banned": "banned", + "users.uid": "UID", + "users.username": "使用者名", + "users.email": "電子郵件", + "users.postcount": "發文數", + "users.reputation": "聲望", + "users.flags": "舉報", + "users.joined": "註冊時間", + "users.last-online": "最後在線", + "users.banned": "封鎖", - "create.username": "User Name", - "create.email": "Email", - "create.email-placeholder": "Email of this user", - "create.password": "Password", - "create.password-confirm": "Confirm Password", + "create.username": "使用者名", + "create.email": "電郵地址", + "create.email-placeholder": "該使用者的郵箱", + "create.password": "密碼", + "create.password-confirm": "確認密碼", - "temp-ban.length": "Ban Length", - "temp-ban.reason": "Reason (Optional)", - "temp-ban.hours": "Hours", - "temp-ban.days": "Days", - "temp-ban.explanation": "Enter the length of time for the ban. Note that a time of 0 will be a considered a permanent ban.", + "temp-ban.length": "停權期間", + "temp-ban.reason": "理由(可選)", + "temp-ban.hours": "小時", + "temp-ban.days": "天", + "temp-ban.explanation": "輸入停權持續時間。提示,時長為0視為永久停權。", - "alerts.confirm-ban": "Do you really want to ban this user permanently?", - "alerts.confirm-ban-multi": "Do you really want to ban these users permanently?", - "alerts.ban-success": "User(s) banned!", - "alerts.button-ban-x": "Ban %1 user(s)", - "alerts.unban-success": "User(s) unbanned!", - "alerts.lockout-reset-success": "Lockout(s) reset!", - "alerts.flag-reset-success": "Flags(s) reset!", - "alerts.no-remove-yourself-admin": "You can't remove yourself as Administrator!", - "alerts.make-admin-success": "User is now administrator.", - "alerts.confirm-remove-admin": "Do you really want to remove this administrator?", - "alerts.remove-admin-success": "User is no longer administrator.", - "alerts.make-global-mod-success": "User is now global moderator.", - "alerts.confirm-remove-global-mod": "Do you really want to remove this global moderator?", - "alerts.remove-global-mod-success": "User is no longer global moderator.", - "alerts.make-moderator-success": "User is now moderator.", - "alerts.confirm-remove-moderator": "Do you really want to remove this moderator?", - "alerts.remove-moderator-success": "User is no longer moderator.", - "alerts.confirm-validate-email": "Do you want to validate email(s) of these user(s)?", - "alerts.confirm-force-password-reset": "Are you sure you want to force the password reset and log out these user(s)?", - "alerts.validate-email-success": "Emails validated", - "alerts.validate-force-password-reset-success": "User(s) passwords have been reset and their existing sessions have been revoked.", - "alerts.password-reset-confirm": "Do you want to send password reset email(s) to these user(s)?", - "alerts.confirm-delete": "Warning!
Do you really want to delete user(s)?
This action is not reversable! Only the user account will be deleted, their posts and topics will remain.", - "alerts.delete-success": "User(s) Deleted!", - "alerts.confirm-purge": "Warning!
Do you really want to delete user(s) and their content?
This action is not reversable! All user data and content will be erased!", - "alerts.create": "Create User", - "alerts.button-create": "Create", - "alerts.button-cancel": "Cancel", - "alerts.error-passwords-different": "Passwords must match!", - "alerts.error-x": "Error

%1

", - "alerts.create-success": "User created!", + "alerts.confirm-ban": "您確定要永久停權該用戶嗎?", + "alerts.confirm-ban-multi": "您確定要永久停權這些用戶嗎?", + "alerts.ban-success": "使用者已停權!", + "alerts.button-ban-x": "停權 %1 名使用者", + "alerts.unban-success": "使用者已復權!", + "alerts.lockout-reset-success": "鎖定已重設!", + "alerts.flag-reset-success": "舉報已重設!", + "alerts.no-remove-yourself-admin": "您無法撤銷自己的管理員身份!", + "alerts.make-admin-success": "該使用者已成為管理員", + "alerts.confirm-remove-admin": "您確定要刪除該管理員?", + "alerts.remove-admin-success": " 該使用者不再是管理員", + "alerts.make-global-mod-success": " 該使用者已成為管理員", + "alerts.confirm-remove-global-mod": "您確定要刪除該超級版主?", + "alerts.remove-global-mod-success": "該使用者已不再是管理員", + "alerts.make-moderator-success": " 該使用者已成為管理員", + "alerts.confirm-remove-moderator": "您確定要刪除該版主?", + "alerts.remove-moderator-success": "該使用者已不再是管理員", + "alerts.confirm-validate-email": "您確定要驗證這些使用者的電郵地址嗎?", + "alerts.confirm-force-password-reset": "你確定您想要向這個(這些)使用者強制密碼重設並退出嗎?", + "alerts.validate-email-success": "電郵地址已驗證", + "alerts.validate-force-password-reset-success": "用戶密碼已經被重設,現存的會話已經被移除", + "alerts.password-reset-confirm": "您確定要向這些使用者發送密碼重設郵件嗎?", + "alerts.confirm-delete": "警告
您確定要刪除這些使用者嗎?
該操作不可逆轉!該操作只會刪除使用者帳戶,他們的回覆與主題仍會保留。", + "alerts.delete-success": "使用者已刪除!", + "alerts.confirm-purge": "警告
您確定要刪除這些使用者和內容嗎?
該操作不可逆轉!選中的所有使用者的資料和內容都將被清除!", + "alerts.create": "建立使用者", + "alerts.button-create": "建立", + "alerts.button-cancel": "取消", + "alerts.error-passwords-different": "兩次輸入的密碼必須相同!", + "alerts.error-x": "錯誤

%1

", + "alerts.create-success": "使用者已建立!", - "alerts.prompt-email": "Emails: ", - "alerts.email-sent-to": "An invitation email has been sent to %1", - "alerts.x-users-found": "%1 user(s) found! Search took %2 ms." + "alerts.prompt-email": "電郵地址:", + "alerts.email-sent-to": "已發送邀請給 %1", + "alerts.x-users-found": "找到 %1 位使用者!搜索耗時 %2 毫秒。" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/menu.json b/public/language/zh-TW/admin/menu.json index 1f275e03ae..4eedbee135 100644 --- a/public/language/zh-TW/admin/menu.json +++ b/public/language/zh-TW/admin/menu.json @@ -1,83 +1,83 @@ { - "section-general": "General", - "general/dashboard": "Dashboard", - "general/homepage": "Home Page", - "general/navigation": "Navigation", - "general/languages": "Languages", - "general/sounds": "Sounds", - "general/social": "Social", + "section-general": "基本", + "general/dashboard": "儀表板", + "general/homepage": "首頁", + "general/navigation": "導航", + "general/languages": "語言", + "general/sounds": "聲音", + "general/social": "社交", - "section-manage": "Manage", - "manage/categories": "Categories", - "manage/privileges": "Privileges", - "manage/tags": "Tags", - "manage/users": "Users", - "manage/admins-mods": "Admins & Mods", - "manage/registration": "Registration Queue", - "manage/post-queue": "Post Queue", - "manage/groups": "Groups", - "manage/ip-blacklist": "IP Blacklist", - "manage/uploads": "Uploads", - "manage/digest": "Digests", + "section-manage": "管理", + "manage/categories": "版面", + "manage/privileges": "權限", + "manage/tags": "標籤", + "manage/users": "使用者", + "manage/admins-mods": "權限分配", + "manage/registration": "註冊申請", + "manage/post-queue": "貼文隊列", + "manage/groups": "群組", + "manage/ip-blacklist": "IP 黑名單", + "manage/uploads": "上傳", + "manage/digest": "摘要", - "section-settings": "Settings", - "settings/general": "General", - "settings/reputation": "Reputation", - "settings/email": "Email", - "settings/user": "User", - "settings/group": "Group", - "settings/guest": "Guests", - "settings/uploads": "Uploads", - "settings/post": "Post", - "settings/chat": "Chat", - "settings/pagination": "Pagination", - "settings/tags": "Tags", - "settings/notifications": "Notifications", + "section-settings": "設定", + "settings/general": "基本", + "settings/reputation": "聲望", + "settings/email": "郵件", + "settings/user": "使用者", + "settings/group": "群組", + "settings/guest": "訪客", + "settings/uploads": "上傳", + "settings/post": "貼文", + "settings/chat": "聊天", + "settings/pagination": "分頁", + "settings/tags": "標籤", + "settings/notifications": "通知", "settings/cookies": "Cookies", - "settings/web-crawler": "Web Crawler", - "settings/sockets": "Sockets", - "settings/advanced": "Advanced", + "settings/web-crawler": "Web 爬蟲", + "settings/sockets": "網路接口(socket)", + "settings/advanced": "進階", - "settings.page-title": "%1 Settings", + "settings.page-title": "1% 設置", - "section-appearance": "Appearance", - "appearance/themes": "Themes", - "appearance/skins": "Skins", - "appearance/customise": "Custom Content (HTML/JS/CSS)", + "section-appearance": "外觀", + "appearance/themes": "佈景主題", + "appearance/skins": "配色", + "appearance/customise": "自訂程式碼 (HTML/JavaScript/CSS)", - "section-extend": "Extend", - "extend/plugins": "Plugins", - "extend/widgets": "Widgets", - "extend/rewards": "Rewards", + "section-extend": "擴展", + "extend/plugins": "外掛", + "extend/widgets": "小工具", + "extend/rewards": "獎勵", - "section-social-auth": "Social Authentication", + "section-social-auth": "社交認證", - "section-plugins": "Plugins", - "extend/plugins.install": "Install Plugins", + "section-plugins": "外掛", + "extend/plugins.install": "已安裝", - "section-advanced": "Advanced", - "advanced/database": "Database", - "advanced/events": "Events", - "advanced/hooks": "Hooks", - "advanced/logs": "Logs", - "advanced/errors": "Errors", - "advanced/cache": "Cache", - "development/logger": "Logger", - "development/info": "Info", + "section-advanced": "進階", + "advanced/database": "資料庫", + "advanced/events": "事件", + "advanced/hooks": "掛鉤(hooks)", + "advanced/logs": "日誌", + "advanced/errors": "錯誤", + "advanced/cache": "快取", + "development/logger": "記錄器", + "development/info": "資訊", - "rebuild-and-restart-forum": "Rebuild & Restart Forum", - "restart-forum": "Restart Forum", - "logout": "Log out", - "view-forum": "View Forum", + "rebuild-and-restart-forum": "部署並重啟論壇", + "restart-forum": "重啟論壇", + "logout": "登出", + "view-forum": "檢視論壇", - "search.placeholder": "Search for settings", - "search.no-results": "No results...", - "search.search-forum": "Search the forum for ", - "search.keep-typing": "Type more to see results...", - "search.start-typing": "Start typing to see results...", + "search.placeholder": "搜尋設定", + "search.no-results": "沒有可用結果…", + "search.search-forum": "搜索論壇為", + "search.keep-typing": "輸入更多以查看結果...", + "search.start-typing": "開始輸入以查看結果...", - "connection-lost": "Connection to %1 has been lost, attempting to reconnect...", + "connection-lost": "與 %1 的連線已中斷,正嘗試重新連接...", - "alerts.version": "Running NodeBB v%1", - "alerts.upgrade": "Upgrade to v%1" + "alerts.version": "正在運行 NodeBB v%1", + "alerts.upgrade": "升級到 v%1" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/advanced.json b/public/language/zh-TW/admin/settings/advanced.json index 4bd6b2aa60..fea28f9002 100644 --- a/public/language/zh-TW/admin/settings/advanced.json +++ b/public/language/zh-TW/admin/settings/advanced.json @@ -1,28 +1,29 @@ { - "maintenance-mode": "Maintenance Mode", - "maintenance-mode.help": "When the forum is in maintenance mode, all requests will be redirected to a static holding page. Administrators are exempt from this redirection, and are able to access the site normally.", - "maintenance-mode.status": "Maintenance Mode Status Code", - "maintenance-mode.message": "Maintenance Message", - "headers": "Headers", - "headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame", - "headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB", + "maintenance-mode": "維護模式", + "maintenance-mode.help": "當論壇處在維護模式時,所有請求將被重導向到一個靜態頁面。管理員不受重導向限制,並可正常訪問網站。", + "maintenance-mode.status": "維護模式狀態碼", + "maintenance-mode.message": "維護訊息", + "headers": "標題", + "headers.allow-from": "設定 ALLOW-FROM 來放置 NodeBB 於 iFrame 中", + "headers.powered-by": "自訂由 NodeBB 發送的 \"Powered By\" 標頭 ", "headers.acao": "Access-Control-Allow-Origin", - "headers.acao-regex": "Access-Control-Allow-Origin Regular Expression", - "headers.acao-help": "To deny access to all sites, leave empty", - "headers.acao-regex-help": "Enter regular expressions here to match dynamic origins. To deny access to all sites, leave empty", + "headers.acao-regex": "Access-Control-Allow-Origin 正規表示法", + "headers.acao-help": "要拒絕所有網站,請留空", + "headers.acao-regex-help": "輸入正規表示法以匹配動態來源。要拒絕所有網站,請留空", "headers.acac": "Access-Control-Allow-Credentials", "headers.acam": "Access-Control-Allow-Methods", "headers.acah": "Access-Control-Allow-Headers", - "hsts": "Strict Transport Security", - "hsts.enabled": "Enabled HSTS (recommended)", - "hsts.subdomains": "Include subdomains in HSTS header", - "hsts.preload": "Allow preloading of HSTS header", - "hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. More information ", - "traffic-management": "Traffic Management", - "traffic.help": "NodeBB deploys equipped with a module that automatically denies requests in high-traffic situations. You can tune these settings here, although the defaults are a good starting point.", - "traffic.enable": "Enable Traffic Management", - "traffic.event-lag": "Event Loop Lag Threshold (in milliseconds)", - "traffic.event-lag-help": "Lowering this value decreases wait times for page loads, but will also show the \"excessive load\" message to more users. (Restart required)", - "traffic.lag-check-interval": "Check Interval (in milliseconds)", - "traffic.lag-check-interval-help": "Lowering this value causes NodeBB to become more sensitive to spikes in load, but may also cause the check to become too sensitive. (Restart required)" + "hsts": "嚴格安全傳輸(HSTS)", + "hsts.enabled": "啟用HSTS(推薦)", + "hsts.maxAge": "HSTS Max Age", + "hsts.subdomains": "HSTS標頭訊息包含的域名", + "hsts.preload": "允許在HSTS標頭中預加載(preloading)", + "hsts.help": "如果啟用此項,網站將會向瀏覽器發送HSTS標頭訊息。您可以設定是否為子域名開啟HSTS,以及HSTS標頭訊息中是否包含預加載標誌(preload參數)如果您不瞭解HSTS,可以忽略此項設定。瞭解詳情 ", + "traffic-management": "流量管理", + "traffic.help": "NodeBB 擁有在高流量情況下自動拒絕請求的模塊。儘管預設值就很棒,但您可以在這裡調整這些設定。", + "traffic.enable": "啟用流量管理", + "traffic.event-lag": "事件循環滯後門檻值(毫秒)", + "traffic.event-lag-help": "降低此值會減少頁面加載的等待時間,但也會向更多使用者顯示“過載”訊息。(需要重新啟動)", + "traffic.lag-check-interval": "檢查間隔(毫秒)", + "traffic.lag-check-interval-help": "降低此值會造成 NodeBB 的負載峰值變得更加敏感,但也可能導致檢查變得過於敏感(需要重新啟動)" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/chat.json b/public/language/zh-TW/admin/settings/chat.json index c1741d101c..73e65609d4 100644 --- a/public/language/zh-TW/admin/settings/chat.json +++ b/public/language/zh-TW/admin/settings/chat.json @@ -1,11 +1,11 @@ { - "chat-settings": "Chat Settings", - "disable": "Disable chat", - "disable-editing": "Disable chat message editing/deletion", - "disable-editing-help": "Administrators and global moderators are exempt from this restriction", - "max-length": "Maximum length of chat messages", - "max-room-size": "Maximum number of users in chat rooms", - "delay": "Time between chat messages in milliseconds", - "restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable. (0 disabled)", - "restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable. (0 disabled)" + "chat-settings": "聊天設定", + "disable": "禁用聊天", + "disable-editing": "禁止編輯/刪除聊天消息", + "disable-editing-help": "管理員和超級版主不受此限制", + "max-length": "聊天訊息的最大長度", + "max-room-size": "聊天室的最多使用者數", + "delay": "聊天訊息間的毫秒數", + "restrictions.seconds-edit-after": "使用者在發佈聊天訊息後允許編輯貼文的秒數(0為禁用)", + "restrictions.seconds-delete-after": "使用者在發佈聊天訊息後允許刪除貼文的秒數(0為禁用)" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/cookies.json b/public/language/zh-TW/admin/settings/cookies.json index a6244febdd..954c7716b7 100644 --- a/public/language/zh-TW/admin/settings/cookies.json +++ b/public/language/zh-TW/admin/settings/cookies.json @@ -1,12 +1,12 @@ { - "eu-consent": "EU Consent", - "consent.enabled": "Enabled", - "consent.message": "Notification message", - "consent.acceptance": "Acceptance message", - "consent.link-text": "Policy Link Text", - "consent.link-url": "Policy Link URL", - "consent.blank-localised-default": "Leave blank to use NodeBB localised defaults", - "settings": "Settings", - "cookie-domain": "Session cookie domain", - "blank-default": "Leave blank for default" + "eu-consent": "歐盟 Cookies 政策", + "consent.enabled": "啟用選項", + "consent.message": "通知訊息", + "consent.acceptance": "接受訊息", + "consent.link-text": "政策連結文字", + "consent.link-url": "政策地址連結", + "consent.blank-localised-default": "留白以便使用 NodeBB 本地預設值", + "settings": "設定", + "cookie-domain": "Session cookie 域名", + "blank-default": "留白以保持預設" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/email.json b/public/language/zh-TW/admin/settings/email.json index cfdcc04a5b..2e4c13bd48 100644 --- a/public/language/zh-TW/admin/settings/email.json +++ b/public/language/zh-TW/admin/settings/email.json @@ -1,40 +1,40 @@ { - "email-settings": "Email Settings", - "address": "Email Address", - "address-help": "The following email address refers to the email that the recipient will see in the \"From\" and \"Reply To\" fields.", - "from": "From Name", - "from-help": "The from name to display in the email.", - "sendmail-rate-limit": "Send X emails...", - "sendmail-rate-delta": "... every X milliseconds", - "sendmail-rate-help": "Instructs the NodeBB mailer to limit the number of messages sent at once in order to not overwhelm email receiving services. These options do not apply if SMTP Transport is enabled (below).", + "email-settings": "郵件設定", + "address": "電子郵件地址", + "address-help": "下面的電子郵件地址代表收件人在“發送人”和“回覆”中所看到的地址。", + "from": "發送人", + "from-help": "用於郵件中顯示的發送人", + "sendmail-rate-limit": "發送X電子郵件", + "sendmail-rate-delta": "。。。每個X毫秒", + "sendmail-rate-help": "立刻設定 NodeBB 郵件程序限制 發送的電子郵件數量,以免壓垮電子郵件接收服務。 如果啟用了SMTP 傳輸,則這些選項不適用(如下所示)。", - "smtp-transport": "SMTP Transport", - "smtp-transport.enabled": "Use an external email server to send emails", - "smtp-transport-help": "You can select from a list of well-known services or enter a custom one.", - "smtp-transport.service": "Select a service", - "smtp-transport.service-custom": "Custom Service", - "smtp-transport.service-help": "Select a service name above in order to use the known information about it. Alternatively, select 'Custom Service' and enter the details below.", - "smtp-transport.gmail-warning1": "There have been reports of the Gmail service not working on accounts with heightened security. In those scenarios, you will have to configure your GMail account to allow less secure apps.", - "smtp-transport.gmail-warning2": "For more information about this workaround, please consult this NodeMailer article on the issue. An alternative would be to utilise a third-party emailer plugin such as SendGrid, Mailgun, etc. Browse available plugins here.", - "smtp-transport.host": "SMTP Host", - "smtp-transport.port": "SMTP Port", - "smtp-transport.security": "Connection security", - "smtp-transport.security-encrypted": "Encrypted", + "smtp-transport": "SMTP 通信", + "smtp-transport.enabled": "使用一個外部電子郵件系統來發送郵件", + "smtp-transport-help": "您可以從列表中選取一個已知的服務或自訂。", + "smtp-transport.service": "選擇服務", + "smtp-transport.service-custom": "自訂", + "smtp-transport.service-help": "選取一個上方服務以便使用已知的訊息。此外,還可以選取 “自訂”並在下方輸入設定細節。", + "smtp-transport.gmail-warning1": "有報告稱,Gmail 代發在安全性更高的帳戶上無法工作。在這種情況下,您需要將您的 Gmail 帳戶設為允許安全性較低的應用程式。", + "smtp-transport.gmail-warning2": "有關此解決方法的更多資訊,請參閱有關該問題的NodeMailer 文章。 另一種方法是利用 SendGrid,Mailgun 等第三方電子郵件外掛。點這兒以瀏覽可用的外掛。", + "smtp-transport.host": "SMTP 主機名", + "smtp-transport.port": "SMTP 通訊埠", + "smtp-transport.security": "連線安全設置", + "smtp-transport.security-encrypted": "加密的", "smtp-transport.security-starttls": "StartTLS", - "smtp-transport.security-none": "None", - "smtp-transport.username": "Username", - "smtp-transport.username-help": "For the Gmail service, enter the full email address here, especially if you are using a Google Apps managed domain.", - "smtp-transport.password": "Password", + "smtp-transport.security-none": "無", + "smtp-transport.username": "使用者名", + "smtp-transport.username-help": "對於Gmail服務,請在這裡輸入完整的電子信箱地址,尤其是如果您使用的是 Google Apps 託管的域名。", + "smtp-transport.password": "密碼", - "template": "Edit Email Template", - "template.select": "Select Email Template", - "template.revert": "Revert to Original", - "testing": "Email Testing", - "testing.select": "Select Email Template", - "testing.send": "Send Test Email", - "testing.send-help": "The test email will be sent to the currently logged in user's email address.", - "subscriptions": "Email Digests", - "subscriptions.disable": "Disable email digests", - "subscriptions.hour": "Digest Hour", - "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent " + "template": "編輯電子郵件樣板", + "template.select": "選擇電子郵件樣板", + "template.revert": "還原為初始樣板", + "testing": "電子郵件測試", + "testing.select": "選擇電子郵件樣板", + "testing.send": "發送測試電子郵件", + "testing.send-help": "測試電子郵件將被發送到當前已登入的使用者的電郵地址。", + "subscriptions": "電子郵件摘要", + "subscriptions.disable": "禁用電子郵件摘要", + "subscriptions.hour": "摘要小時", + "subscriptions.hour-help": "請輸入一個代表小時的數字來發送排程的電子郵件摘要 (例如,對於午夜,0,對於下午5:00,17)。 請記住,這是根據伺服器本身的時間,可能與您的系統時鐘不完全符合。
伺服器的大致時間為:
下一個每日摘要被排程在發送" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/general.json b/public/language/zh-TW/admin/settings/general.json index 8a38bf856f..4a514d5847 100644 --- a/public/language/zh-TW/admin/settings/general.json +++ b/public/language/zh-TW/admin/settings/general.json @@ -1,35 +1,41 @@ { "site-settings": "網站設定", "title": "網站標題", + "title.short": "Short Title", + "title.short-placeholder": "If no short title is specified, the site title will be used", "title.url": "網址", - "title.url-placeholder": "網站標題連結網址", - "title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", - "title.name": "你的社群(論壇)名稱", - "title.show-in-header": "於頂端(header)顯示網站標題", + "title.url-placeholder": "網站標題連結", + "title.url-help": "當標題被點擊,使用者將轉導到該地址。如果留白,使用者將跳轉到論壇首頁。", + "title.name": "您的社區名稱", + "title.show-in-header": "在頂部顯示網站標題", "browser-title": "瀏覽器標題", - "browser-title-help": "若未設定瀏覽器標題,則使用網站標題顯示。", - "title-layout": "標題Layout", - "title-layout-help": "Define how the browser title will be structured ie. {pageTitle} | {browserTitle}", - "description.placeholder": "A short description about your community", - "description": "Site Description", - "keywords": "Site Keywords", - "keywords-placeholder": "Keywords describing your community, comma-separated", - "logo": "Site Logo", - "logo.image": "Image", - "logo.image-placeholder": "Path to a logo to display on forum header", - "logo.upload": "Upload", - "logo.url": "URL", - "logo.url-placeholder": "The URL of the site logo", - "logo.url-help": "When the logo is clicked, send users to this address. If left blank, user will be sent to the forum index.", - "logo.alt-text": "Alt Text", - "log.alt-text-placeholder": "Alternative text for accessibility", - "favicon": "Favicon", + "browser-title-help": "如果沒有指定瀏覽器標題,將會使用網站標題", + "title-layout": "標題佈局", + "title-layout-help": "定義瀏覽器標題的佈局,即{pageTitle} | {browserTitle}", + "description.placeholder": "關於您的社區的簡短說明", + "description": "網站描述", + "keywords": "網站關鍵字", + "keywords-placeholder": "描述您的社區的關鍵字(以逗號分隔)", + "logo": "網站 Logo", + "logo.image": "圖檔", + "logo.image-placeholder": "要在論壇標題上顯示的 Logo 的路徑", + "logo.upload": "上傳", + "logo.url": "網址", + "logo.url-placeholder": "網站 Logo 連結", + "logo.url-help": "當 Logo 被點擊時,將使用者轉導到此地址。如果留白,使用者將被轉導到論壇首頁。", + "logo.alt-text": "替代文字", + "log.alt-text-placeholder": "輔助功能的替代文字", + "favicon": "網站圖示", "favicon.upload": "上傳", - "touch-icon": "Homescreen/Touch Icon", + "touch-icon": "主螢幕/觸控圖示", "touch-icon.upload": "上傳", - "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", + "touch-icon.help": "推薦的尺寸和格式:192x192,僅限PNG格式。 如果沒有指定觸控圖示,NodeBB將改用網站圖示。", + "outgoing-links": "站外連結", + "outgoing-links.warning-page": "使用站外連結警告頁", "search-default-sort-by": "預設搜尋排序", - "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page" -} \ No newline at end of file + "outgoing-links.whitelist": "新增域名到白名單以繞過警告頁面", + "site-colors": "Site Color Metadata", + "theme-color": "Theme Color", + "background-color": "Background Color", + "background-color-help": "Color used for splash screen background when website is installed as a PWA" +} diff --git a/public/language/zh-TW/admin/settings/group.json b/public/language/zh-TW/admin/settings/group.json index e17b1aa434..e4995dd56a 100644 --- a/public/language/zh-TW/admin/settings/group.json +++ b/public/language/zh-TW/admin/settings/group.json @@ -1,12 +1,13 @@ { - "general": "一般", - "private-groups": "私人群組", - "private-groups.help": "如果開啟,加入群組需要經過群組擁有者批準 (預設: enabled)", - "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", - "allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.", - "max-name-length": "群組最大名稱長度", - "max-title-length": "Maximum Group Title Length", - "cover-image": "群組封面圖像", - "default-cover": "預設封面圖像", - "default-cover-help": "Add comma-separated default cover images for groups that don't have an uploaded cover image" + "general": "基本", + "private-groups": "私有群組", + "private-groups.help": "啟用此選項後,加入群組需要群組所有者核可(預設啟用)。", + "private-groups.warning": "注意!如果這個選項未啟用並且你有私有群組,那麼你的群組將變為公共的。", + "allow-multiple-badges": "允許多個徽章", + "allow-multiple-badges-help": "啟用此選項後,使用者可以選擇顯示多個群組徽章,需要主題支持。", + "max-name-length": "群組名字的最大長度", + "max-title-length": "群組標題最大長度", + "cover-image": "群組封面圖片", + "default-cover": "預設封面圖片", + "default-cover-help": "為沒有上傳封面圖片的群組增加以逗號分隔的預設封面圖片" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/guest.json b/public/language/zh-TW/admin/settings/guest.json index a8b9d458f0..42c80615be 100644 --- a/public/language/zh-TW/admin/settings/guest.json +++ b/public/language/zh-TW/admin/settings/guest.json @@ -1,5 +1,5 @@ { - "handles": "Guest Handles", - "handles.enabled": "Allow guest handles", - "handles.enabled-help": "This option exposes a new field that allows guests to pick a name to associate with each post they make. If disabled, they will simply be called \"Guest\"" + "handles": "訪客使用者名", + "handles.enabled": "允許訪客使用者名", + "handles.enabled-help": "這個選項將允許訪客使用一個額外的輸入框來設置發文時的使用者名,如果被禁用,僅會統一顯示為“訪客”" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/notifications.json b/public/language/zh-TW/admin/settings/notifications.json index 5e525a6689..15e5b20b8e 100644 --- a/public/language/zh-TW/admin/settings/notifications.json +++ b/public/language/zh-TW/admin/settings/notifications.json @@ -1,7 +1,7 @@ { - "notifications": "告示", - "welcome-notification": "歡迎告示", - "welcome-notification-link": "歡迎告示連結", - "welcome-notification-uid": "Welcome Notification User (UID)", - "notification-alert-timeout": "Notification Alert Timeout" + "notifications": "通知", + "welcome-notification": "歡迎通知", + "welcome-notification-link": "歡迎通知連結", + "welcome-notification-uid": "歡迎通知使用者 (UID)", + "notification-alert-timeout": "通知與警吿逾時時間" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/pagination.json b/public/language/zh-TW/admin/settings/pagination.json index 3eb210243e..359db9345b 100644 --- a/public/language/zh-TW/admin/settings/pagination.json +++ b/public/language/zh-TW/admin/settings/pagination.json @@ -1,10 +1,10 @@ { - "pagination": "Pagination Settings", - "enable": "Paginate topics and posts instead of using infinite scroll.", - "topics": "Topic Pagination", - "posts-per-page": "Posts per Page", - "max-posts-per-page": "Maximum posts per page", - "categories": "Category Pagination", - "topics-per-page": "Topics per Page", - "max-topics-per-page": "Maximum topics per page" + "pagination": "分頁設定", + "enable": "在主題和文章使用分頁替代無限滾動瀏覽。", + "topics": "主題分頁", + "posts-per-page": "每頁文章數", + "max-posts-per-page": "每頁最多文章數", + "categories": "版面分頁", + "topics-per-page": "每頁主題數", + "max-topics-per-page": "每頁最多主題數" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/post.json b/public/language/zh-TW/admin/settings/post.json index da708f4c6c..0254e52b3a 100644 --- a/public/language/zh-TW/admin/settings/post.json +++ b/public/language/zh-TW/admin/settings/post.json @@ -1,59 +1,59 @@ { - "sorting": "Post Sorting", - "sorting.post-default": "Default Post Sorting", - "sorting.oldest-to-newest": "Oldest to Newest", - "sorting.newest-to-oldest": "Newest to Oldest", - "sorting.most-votes": "Most Votes", - "sorting.most-posts": "Most Posts", - "sorting.topic-default": "Default Topic Sorting", - "length": "Post Length", - "post-queue": "Post Queue", - "restrictions": "Posting Restrictions", - "restrictions-new": "New User Restrictions", - "restrictions.post-queue": "Enable post queue", - "restrictions.post-queue-rep-threshold": "Reputation required to bypass post queue", - "restrictions.groups-exempt-from-post-queue": "Select groups that should be exempt from the post queue", - "restrictions-new.post-queue": "Enable new user restrictions", - "restrictions.post-queue-help": "Enabling post queue will put the posts of new users in a queue for approval", - "restrictions-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users", - "restrictions.seconds-between": "Number of seconds between posts", - "restrictions.seconds-between-new": "Seconds between posts for new users", - "restrictions.rep-threshold": "Reputation threshold before these restrictions are lifted", - "restrictions.seconds-before-new": "Seconds before a new user can make their first post", - "restrictions.seconds-edit-after": "Number of seconds a post remains editable (set to 0 to disable)", - "restrictions.seconds-delete-after": "Number of seconds a post remains deletable (set to 0 to disable)", - "restrictions.replies-no-delete": "Number of replies after users are disallowed to delete their own topics (set to 0 to disable)", - "restrictions.min-title-length": "Minimum Title Length", - "restrictions.max-title-length": "Maximum Title Length", - "restrictions.min-post-length": "Minimum Post Length", - "restrictions.max-post-length": "Maximum Post Length", - "restrictions.days-until-stale": "Days until topic is considered stale", - "restrictions.stale-help": "If a topic is considered \"stale\", then a warning will be shown to users who attempt to reply to that topic.", - "timestamp": "Timestamp", - "timestamp.cut-off": "Date cut-off (in days)", - "timestamp.cut-off-help": "Dates & times will be shown in a relative manner (e.g. \"3 hours ago\" / \"5 days ago\"), and localised into various\n\t\t\t\t\tlanguages. After a certain point, this text can be switched to display the localised date itself\n\t\t\t\t\t(e.g. 5 Nov 2016 15:30).
(Default: 30, or one month). Set to 0 to always display dates, leave blank to always display relative times.", - "timestamp.necro-threshold": "Necro Threshold (in days)", - "timestamp.necro-threshold-help": "A message will be shown between posts if the time between them is longer than the necro threshold. (Default: 7, or one week). Set to 0 to disable.", - "teaser": "Teaser Post", - "teaser.last-post": "Last – Show the latest post, including the original post, if no replies", - "teaser.last-reply": "Last – Show the latest reply, or a \"No replies\" placeholder if no replies", - "teaser.first": "First", - "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", - "signature.no-images": "Disable images in signatures", - "signature.max-length": "Maximum Signature Length", - "composer": "Composer Settings", - "composer-help": "The following settings govern the functionality and/or appearance of the post composer shown\n\t\t\t\tto users when they create new topics, or reply to existing topics.", - "composer.show-help": "Show \"Help\" tab", - "composer.enable-plugin-help": "Allow plugins to add content to the help tab", - "composer.custom-help": "Custom Help Text", - "ip-tracking": "IP Tracking", - "ip-tracking.each-post": "Track IP Address for each post", - "enable-post-history": "Enable Post History" + "sorting": "貼文排序", + "sorting.post-default": "預設貼文排序", + "sorting.oldest-to-newest": "從舊到新", + "sorting.newest-to-oldest": "從新到舊", + "sorting.most-votes": "最多點贊", + "sorting.most-posts": "最多回覆", + "sorting.topic-default": "預設主題排序", + "length": "貼文字數", + "post-queue": "貼文隊列", + "restrictions": "貼文限制", + "restrictions-new": "新使用者限制", + "restrictions.post-queue": "啟用貼文隊列", + "restrictions.post-queue-rep-threshold": "忽略貼文隊列的聲望值", + "restrictions.groups-exempt-from-post-queue": "選擇豁免貼文隊列的群組", + "restrictions-new.post-queue": "啟用新使用者限制", + "restrictions.post-queue-help": "啟用貼文審查會將新使用者的貼文放入審查佇列", + "restrictions-new.post-queue-help": "啟用新使用者限制將對新使用者張貼的文章設定限制", + "restrictions.seconds-between": "貼文間隔時間(秒)", + "restrictions.seconds-between-new": "新使用者貼文間隔時間(秒)", + "restrictions.rep-threshold": "取消貼文限制所需的聲望值", + "restrictions.seconds-before-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.stale-help": "如果某個主題被視為“過時”,則會向嘗試回覆該主題的使用者顯示警告。", + "timestamp": "時間郵戳", + "timestamp.cut-off": "截止日期(天)", + "timestamp.cut-off-help": "日期&時間將以相對方式 (例如,“3小時前” / “5天前”) 顯示,並且會依照訪客語言時區轉換。在某一時刻之後,可以切換該文字以顯示本地化日期本身 (例如2016年11月5日15:30) 。
(預設值: 30 或一個月) 。 設定為0可總是顯示日期,留白以總是顯示相對時間。", + "timestamp.necro-threshold": "挖墳警告(單位:天)", + "timestamp.necro-threshold-help": "若進行回覆的貼文最後回覆的時間早於挖墳警告設定的天數,則在嘗試回覆前顯示挖墳警告(預設:7天)。可以設定為 0 來禁用。", + "teaser": "貼文預覽", + "teaser.last-post": "最後– 顯示最新的貼文,包括原帖,如果沒有回覆", + "teaser.last-reply": "最後– 顯示最新回覆,如果沒有回覆,則顯示“無回覆”佔位符", + "teaser.first": "第一", + "unread": "未讀設定", + "unread.cutoff": "未讀截止時間(天)", + "unread.min-track-last": "跟蹤最後閱讀之前的主題最小貼文", + "recent": "最近設定", + "recent.categoryFilter.disable": "禁用對 /recent 頁面上忽略版面中的主題進行過濾", + "signature": "簽名設定", + "signature.disable": "禁用簽名", + "signature.no-links": "禁用簽名中的連結", + "signature.no-images": "禁用簽名中的圖片", + "signature.max-length": "簽名字數上限", + "composer": "編輯器設定", + "composer-help": "以下設定控制所示後期編輯器的功能和/或外觀\n\t\t\t\t當使用者建立新主題或回覆現有主題時。", + "composer.show-help": "顯示“幫助”選項卡", + "composer.enable-plugin-help": "允許外掛添加內容到幫助選項卡", + "composer.custom-help": "自訂幫助文字", + "ip-tracking": "IP 跟蹤", + "ip-tracking.each-post": "跟蹤每個貼文的 IP 地址", + "enable-post-history": "啟用回覆歷史" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/reputation.json b/public/language/zh-TW/admin/settings/reputation.json index 910909ff65..3d8af89a02 100644 --- a/public/language/zh-TW/admin/settings/reputation.json +++ b/public/language/zh-TW/admin/settings/reputation.json @@ -1,14 +1,14 @@ { - "reputation": "Reputation Settings", - "disable": "Disable Reputation System", - "disable-down-voting": "Disable Down Voting", - "votes-are-public": "All Votes Are Public", - "thresholds": "Activity Thresholds", - "min-rep-downvote": "Minimum reputation to downvote posts", - "min-rep-flag": "Minimum reputation to flag posts", - "min-rep-website": "Minimum reputation to add \"Website\" to user profile", - "min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile", - "min-rep-signature": "Minimum reputation to add \"Signature\" to user profile", - "min-rep-profile-picture": "Minimum reputation to add \"Profile Picture\" to user profile", - "min-rep-cover-picture": "Minimum reputation to add \"Cover Picture\" to user profile" + "reputation": "聲望設定", + "disable": "停用 聲望系統", + "disable-down-voting": "停用 倒讚", + "votes-are-public": "公開所有讚", + "thresholds": "操作限制", + "min-rep-downvote": "倒讚貼文 需要的最低聲望", + "min-rep-flag": "舉報貼文 需要的最低聲望", + "min-rep-website": "加入 個人網站 需要的最低聲望", + "min-rep-aboutme": "加入 個人 “關於我”頁 需要的最低聲望", + "min-rep-signature": "加入 簽名檔 需要的最低聲望", + "min-rep-profile-picture": "加入 個人頭像 需要的最低聲望", + "min-rep-cover-picture": "加入 個人封面圖片 需要的最低聲望" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/tags.json b/public/language/zh-TW/admin/settings/tags.json index d329cff68b..f93f690824 100644 --- a/public/language/zh-TW/admin/settings/tags.json +++ b/public/language/zh-TW/admin/settings/tags.json @@ -1,10 +1,10 @@ { - "tag": "Tag Settings", - "min-per-topic": "Minimum Tags per Topic", - "max-per-topic": "Maximum Tags per Topic", - "min-length": "Minimum Tag Length", - "max-length": "Maximum Tag Length", - "goto-manage": "Click here to visit the tag management page.", - "related-topics": "Related Topics", - "max-related-topics": "Maximum related topics to display (if supported by theme)" + "tag": "標籤設定", + "min-per-topic": "每個主題的最少標籤數", + "max-per-topic": "每話題的最大標籤數", + "min-length": "最短標籤長度", + "max-length": "最大標籤長度", + "goto-manage": "點擊這裡訪問標籤管理頁面。", + "related-topics": "相關主題", + "max-related-topics": "最大相關主題顯示量(如果主題支持)" } \ No newline at end of file diff --git a/public/language/zh-TW/admin/settings/uploads.json b/public/language/zh-TW/admin/settings/uploads.json index 34da585485..067b3c3d17 100644 --- a/public/language/zh-TW/admin/settings/uploads.json +++ b/public/language/zh-TW/admin/settings/uploads.json @@ -1,39 +1,39 @@ { - "posts": "Posts", - "allow-files": "Allow users to upload regular files", - "private": "Make uploaded files private", - "strip-exif-data": "Strip EXIF Data", - "private-extensions": "File extensions to make private", - "private-uploads-extensions-help": "Enter comma-separated list of file extensions to make private here (e.g. pdf,xls,doc). An empty list means all files are private.", - "resize-image-width-threshold": "Resize images if they are wider than specified width", - "resize-image-width-threshold-help": "(in pixels, default: 1520 pixels, set to 0 to disable)", - "resize-image-width": "Resize images down to specified width", - "resize-image-width-help": "(in pixels, default: 760 pixels, set to 0 to disable)", - "resize-image-quality": "Quality to use when resizing images", - "resize-image-quality-help": "Use a lower quality setting to reduce the file size of resized images.", - "max-file-size": "Maximum File Size (in KiB)", - "max-file-size-help": "(in kibibytes, default: 2048 KiB)", - "reject-image-width": "Maximum Image Width (in pixels)", - "reject-image-width-help": "Images wider than this value will be rejected.", - "reject-image-height": "Maximum Image Height (in pixels)", - "reject-image-height-help": "Images taller than this value will be rejected.", - "allow-topic-thumbnails": "Allow users to upload topic thumbnails", - "topic-thumb-size": "Topic Thumb Size", - "allowed-file-extensions": "Allowed File Extensions", - "allowed-file-extensions-help": "Enter comma-separated list of file extensions here (e.g. pdf,xls,doc). An empty list means all extensions are allowed.", - "profile-avatars": "Profile Avatars", - "allow-profile-image-uploads": "Allow users to upload profile images", - "convert-profile-image-png": "Convert profile image uploads to PNG", - "default-avatar": "Custom Default Avatar", - "upload": "Upload", - "profile-image-dimension": "Profile Image Dimension", - "profile-image-dimension-help": "(in pixels, default: 128 pixels)", - "max-profile-image-size": "Maximum Profile Image File Size", - "max-profile-image-size-help": "(in kibibytes, default: 256 KiB)", - "max-cover-image-size": "Maximum Cover Image File Size", - "max-cover-image-size-help": "(in kibibytes, default: 2,048 KiB)", - "keep-all-user-images": "Keep old versions of avatars and profile covers on the server", - "profile-covers": "Profile Covers", - "default-covers": "Default Cover Images", - "default-covers-help": "Add comma-separated default cover images for accounts that don't have an uploaded cover image" + "posts": "貼文", + "allow-files": "允許使用者上傳普通檔案", + "private": "使上傳的檔案私有化", + "strip-exif-data": "去除 EXIF 資料", + "private-extensions": "自訂檔案附檔名", + "private-uploads-extensions-help": "在此處輸入以逗號分隔的副檔名列表 (例如 pdf,xls,doc )並將其用於自訂。為空則表示允許所有副檔名。", + "resize-image-width-threshold": "如果圖片寬度超過指定大小,則對圖片進行縮放", + "resize-image-width-threshold-help": "(像素單位,預設 1520 px,設定為0以停用)", + "resize-image-width": "縮小圖片到指定寬度", + "resize-image-width-help": "(像素單位,預設 760 px,設定為0以停用)", + "resize-image-quality": "調整圖片大小時使用的品質", + "resize-image-quality-help": "使用較低品質的設定來減小調整過大小的圖片的檔案大小", + "max-file-size": "最大檔案大小(單位 KiB)", + "max-file-size-help": "(單位 KiB ,預設 2048KiB)", + "reject-image-width": "圖片最大寬度值(單位:像素)", + "reject-image-width-help": "寬於此數值大小的圖片將會被拒絕", + "reject-image-height": "圖片最大高度值(單位:像素)", + "reject-image-height-help": "高於此數值大小的圖片將會被拒絕", + "allow-topic-thumbnails": "允許使用者上傳主題縮圖", + "topic-thumb-size": "主題縮圖大小", + "allowed-file-extensions": "允許的副檔名", + "allowed-file-extensions-help": "在此處輸入以逗號分隔的副檔名列表 (例如 pdf,xls,doc )。 為空則表示允許所有副檔名。", + "profile-avatars": "個人頭像", + "allow-profile-image-uploads": "允許使用者上傳個人頭像", + "convert-profile-image-png": "轉換個人頭像為 PNG 格式", + "default-avatar": "訪客預設頭像", + "upload": "上傳", + "profile-image-dimension": "個人頭像尺寸", + "profile-image-dimension-help": "(使用 px 作為單位,預設:128px)", + "max-profile-image-size": "個人頭像最大大小", + "max-profile-image-size-help": "(單位 KiB ,預設 256 KiB)", + "max-cover-image-size": "最大封面圖片檔案大小", + "max-cover-image-size-help": "(單位 KiB,預設 2048KiB)", + "keep-all-user-images": "在伺服器上保留舊頭像和舊的個人封面", + "profile-covers": "個人封面", + "default-covers": "預設封面圖片", + "default-covers-help": "為沒有上傳封面圖片的帳戶添加以逗號分隔的預設封面圖片" } diff --git a/public/language/zh-TW/admin/settings/user.json b/public/language/zh-TW/admin/settings/user.json index 9f029bf777..e3ef5ea141 100644 --- a/public/language/zh-TW/admin/settings/user.json +++ b/public/language/zh-TW/admin/settings/user.json @@ -1,79 +1,79 @@ { - "authentication": "Authentication", - "require-email-confirmation": "Require Email Confirmation", - "email-confirm-interval": "User may not resend a confirmation email until", - "email-confirm-email2": "minutes have elapsed", - "allow-login-with": "Allow login with", - "allow-login-with.username-email": "Username or Email", - "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", - "account-settings": "Account Settings", - "gdpr_enabled": "Enable GDPR consent collection", - "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", - "disable-username-changes": "Disable username changes", - "disable-email-changes": "Disable email changes", - "disable-password-changes": "Disable password changes", - "allow-account-deletion": "Allow account deletion", - "hide-fullname": "Hide fullname from users", - "hide-email": "Hide email from users", - "themes": "Themes", - "disable-user-skins": "Prevent users from choosing a custom skin", - "account-protection": "Account Protection", - "admin-relogin-duration": "Admin relogin duration (minutes)", - "admin-relogin-duration-help": "After a set amount of time accessing the admin section will require re-login, set to 0 to disable", - "login-attempts": "Login attempts per hour", - "login-attempts-help": "If login attempts to a user's account exceeds this threshold, that account will be locked for a pre-configured amount of time", - "lockout-duration": "Account Lockout Duration (minutes)", - "login-days": "Days to remember user login sessions", - "password-expiry-days": "Force password reset after a set number of days", - "session-time": "Session Time", - "session-time-days": "Days", - "session-time-seconds": "Seconds", - "session-time-help": "These values are used to govern how long a user stays logged in when they check "Remember Me" on login. Note that only one of these values will be used. If there is no seconds value we fall back to days. If there is no days value we default to 14 days.", - "online-cutoff": "Minutes after user is considered inactive", - "online-cutoff-help": "If user performs no actions for this duration, they are considered inactive and they do not receive realtime updates.", - "registration": "User Registration", - "registration-type": "Registration Type", - "registration-approval-type": "Registration Approval Type", - "registration-type.normal": "Normal", - "registration-type.admin-approval": "Admin Approval", - "registration-type.admin-approval-ip": "Admin Approval for IPs", - "registration-type.invite-only": "Invite Only", - "registration-type.admin-invite-only": "Admin Invite Only", - "registration-type.disabled": "No registration", - "registration-type.help": "Normal - Users can register from the /register page.
\nInvite Only - Users can invite others from the users page.
\nAdmin Invite Only - Only administrators can invite others from users and admin/manage/users pages.
\nNo registration - No user registration.
", - "registration-approval-type.help": "Normal - Users are registered immediately.
\nAdmin Approval - User registrations are placed in an approval queue for administrators.
\nAdmin Approval for IPs - Normal for new users, Admin Approval for IP addresses that already have an account.
", - "registration.max-invites": "Maximum Invitations per User", - "max-invites": "Maximum Invitations per User", - "max-invites-help": "0 for no restriction. Admins get infinite invitations
Only applicable for \"Invite Only\"", - "invite-expiration": "Invite expiration", - "invite-expiration-help": "# of days invitations expire in.", - "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", - "user-search-results-per-page": "Number of results to display", - "default-user-settings": "Default User Settings", - "show-email": "Show email", - "show-fullname": "Show fullname", - "restrict-chat": "Only allow chat messages from users I follow", - "outgoing-new-tab": "Open outgoing links in new tab", - "topic-search": "Enable In-Topic Searching", - "digest-freq": "Subscribe to Digest", - "digest-freq.off": "Off", - "digest-freq.daily": "Daily", - "digest-freq.weekly": "Weekly", - "digest-freq.monthly": "Monthly", - "email-chat-notifs": "Send an email if a new chat message arrives and I am not online", - "email-post-notif": "Send an email when replies are made to topics I am subscribed to", - "follow-created-topics": "Follow topics you create", - "follow-replied-topics": "Follow topics that you reply to", - "default-notification-settings": "Default notification settings", - "categoryWatchState": "Default category watch state", - "categoryWatchState.watching": "Watching", - "categoryWatchState.notwatching": "Not Watching", - "categoryWatchState.ignoring": "Ignoring" -} \ No newline at end of file + "authentication": "驗證", + "require-email-confirmation": "需要電子信箱確認", + "email-confirm-interval": "使用者無法重新發送電子信箱確認信直到", + "email-confirm-email2": "分鐘已經過", + "allow-login-with": "允許使用何種登入名", + "allow-login-with.username-email": "使用者名或者電子信箱", + "allow-login-with.username": "僅限使用者名", + "allow-login-with.email": "僅限電子信箱", + "account-settings": "使用者設定", + "gdpr_enabled": "啟用通用資料保護條例(GDPR)許可的個人資料收集", + "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", + "disable-username-changes": "停用修改使用者名", + "disable-email-changes": "停用修改電子信箱", + "disable-password-changes": "停用修改密碼", + "allow-account-deletion": "允許刪除帳戶", + "hide-fullname": "隱藏使用者的全名", + "hide-email": "隱藏使用者的電子信箱", + "themes": "佈景主題", + "disable-user-skins": "阻止使用者選擇自訂配色", + "account-protection": "帳戶保護", + "admin-relogin-duration": "管理員無操作自動退出持續時間 (分鐘)", + "admin-relogin-duration-help": "訪問控制面板一段時間後需要重新登入以保證控制面板的安全,設定為0以停用。", + "login-attempts": "每小時嘗試登入次數", + "login-attempts-help": "如果使用者的嘗試登入次數超過此界限,該帳戶將會被被鎖定預設的時間。", + "lockout-duration": "帳戶鎖定時間(分鐘)", + "login-days": "記錄使用者會話天數", + "password-expiry-days": "強制重設密碼天數", + "session-time": "Session 過期時間", + "session-time-days": "天", + "session-time-seconds": "秒", + "session-time-help": "這些值將用於控制使用者在登入時選中"記住我"後能夠保持登入的持續時間。注意以下數字中只有一個將被使用。若值為空我們將改為使用。若值為空我們將使用預設值14天。", + "online-cutoff": "分鐘後認定使用者已離線", + "online-cutoff-help": "若使用者在此時間後未作出任何動作,他們將被視為不活躍狀態且不會收到即時更新。", + "registration": "使用者註冊", + "registration-type": "註冊方式", + "registration-approval-type": "註冊批准類型", + "registration-type.normal": "一般", + "registration-type.admin-approval": "管理員批准", + "registration-type.admin-approval-ip": "管理員批准 IP地址", + "registration-type.invite-only": "僅限邀請", + "registration-type.admin-invite-only": "僅限管理員邀請", + "registration-type.disabled": "停用註冊", + "registration-type.help": "一般 - 使用者可以通過 /register頁面註冊
\n管理員批准 - 使用者註冊請求會被放入 請求佇列待管理員批准。
\n邀請制 - 使用者可以通過 使用者 頁面邀請其他使用者。\n管理員邀請制 - 只有管理員可以通過 使用者admin/manage/users 頁面邀請其他使用者。
\n停用註冊 - 不開放用戶註冊。
", + "registration-approval-type.help": "通常 - 用戶可以通過/register頁面註冊
\n管理員批准 - 用戶註冊請求會被放入 請求隊列 待管理員批准。
\n管理員批准 IP地址 - 新用戶不受影響,已存在賬號的IP地址註冊需要管理員批准。
", + "registration.max-invites": "每個使用者最大邀請數", + "max-invites": "每個使用者最大邀請數", + "max-invites-help": "無限制填 0 。管理員沒有邀請限制
僅在邀請制時可用", + "invite-expiration": "邀請過期", + "invite-expiration-help": "邀請在#日過期。", + "min-username-length": "最小使用者名長度", + "max-username-length": "最大使用者名長度", + "min-password-length": "最小密碼長度", + "min-password-strength": "最小密碼強度", + "max-about-me-length": "自我介紹的最大長度", + "terms-of-use": "論壇使用條款 (留空即可禁用)", + "user-search": "用戶搜尋", + "user-search-results-per-page": "顯示的結果數量", + "default-user-settings": "預設使用者設定", + "show-email": "顯示郵箱", + "show-fullname": "顯示全名", + "restrict-chat": "只允許我追隨的使用者給我發送聊天訊息", + "outgoing-new-tab": "在新頁籤打開外部連結", + "topic-search": "啟用主題內搜尋", + "digest-freq": "訂閱摘要", + "digest-freq.off": "關閉", + "digest-freq.daily": "每日", + "digest-freq.weekly": "每週", + "digest-freq.monthly": "每月", + "email-chat-notifs": "當我不在線並收到新的聊天訊息時,給我發送電郵通知", + "email-post-notif": "當我訂閱的主題有新回覆時,給我發送電郵通知", + "follow-created-topics": "關注您建立的主題", + "follow-replied-topics": "關注您回覆的主題", + "default-notification-settings": "預設通知設定", + "categoryWatchState": "預設版面關注狀態", + "categoryWatchState.watching": "已關注", + "categoryWatchState.notwatching": "未關注", + "categoryWatchState.ignoring": "已忽略" +} diff --git a/public/language/zh-TW/admin/settings/web-crawler.json b/public/language/zh-TW/admin/settings/web-crawler.json index 2e0d31d12b..04033ccc7c 100644 --- a/public/language/zh-TW/admin/settings/web-crawler.json +++ b/public/language/zh-TW/admin/settings/web-crawler.json @@ -1,10 +1,10 @@ { - "crawlability-settings": "Crawlability Settings", - "robots-txt": "Custom Robots.txt Leave blank for default", - "sitemap-feed-settings": "Sitemap & Feed Settings", - "disable-rss-feeds": "Disable RSS Feeds", - "disable-sitemap-xml": "Disable Sitemap.xml", - "sitemap-topics": "Number of Topics to display in the Sitemap", - "clear-sitemap-cache": "Clear Sitemap Cache", - "view-sitemap": "View Sitemap" + "crawlability-settings": "爬蟲抓取設定", + "robots-txt": "自訂 Robots.txt,留白以使用預設設定", + "sitemap-feed-settings": "網站地圖與訂閱設定", + "disable-rss-feeds": "停用 RSS 訂閱", + "disable-sitemap-xml": "停用 Sitemap.xml", + "sitemap-topics": "要在 Sitemap 中展現的主題數量", + "clear-sitemap-cache": "清除 Sitemap 快取", + "view-sitemap": "檢視 Sitemap" } \ No newline at end of file diff --git a/public/language/zh-TW/category.json b/public/language/zh-TW/category.json index 271f0656fd..65df514c23 100644 --- a/public/language/zh-TW/category.json +++ b/public/language/zh-TW/category.json @@ -1,22 +1,22 @@ { - "category": "類別", - "subcategories": "子類別", - "new_topic_button": "新主題", - "guest-login-post": "登入後才能發文", - "no_topics": "此類別目前尚無主題。
不然你發表一篇文章好嗎?", + "category": "版面", + "subcategories": "子版面", + "new_topic_button": "發表主題", + "guest-login-post": "登入以發表", + "no_topics": "此版面還沒有任何內容。
趕緊來貼文吧!", "browsing": "正在瀏覽", - "no_replies": "還沒有回覆", - "no_new_posts": "沒有新的文章", + "no_replies": "尚無回覆", + "no_new_posts": "沒有新主題", "watch": "關注", "ignore": "忽略", - "watching": "關注中", + "watching": "已關注", "not-watching": "未關注", "ignoring": "已忽略", - "watching.description": "顯示未讀的最新主題", - "not-watching.description": "顯示最新的主題,不要顯示未讀的主題。", - "ignoring.description": "不要顯示未讀的最新主題", - "watching.message": "您正在觀看此類別和所有子類別的更新。", - "notwatching.message": "您沒有觀看此類別和所有子類別的更新。", - "ignoring.message": "您正在忽略此類別與所有子類別的更新", - "watched-categories": "關注的類別" + "watching.description": "顯示未讀和最近的主題", + "not-watching.description": "不顯示未讀主題,顯示最近主題", + "ignoring.description": "不顯示未讀和最近的主題", + "watching.message": "您關注了此版面和全部子版面的動態。", + "notwatching.message": "您未關注了此版面和全部子版面的動態。", + "ignoring.message": "您忽略了此版面和全部子版面的動態。", + "watched-categories": "已關注的版面" } \ No newline at end of file diff --git a/public/language/zh-TW/email.json b/public/language/zh-TW/email.json index b05ca78615..6011ace9d6 100644 --- a/public/language/zh-TW/email.json +++ b/public/language/zh-TW/email.json @@ -1,52 +1,52 @@ { - "test-email.subject": "測試電子郵件", - "password-reset-requested": "已要求重設密碼!", + "test-email.subject": "測試郵件", + "password-reset-requested": "已申請密碼重設!", "welcome-to": "歡迎來到 %1", - "invite": "邀請來自 %1", - "greeting_no_name": "你好", - "greeting_with_name": "%1 你好", - "email.verify-your-email.subject": "請驗證你的電子郵件", - "email.verify.text1": "你的電郵地址已變更!", - "welcome.text1": "感謝註冊 %1!", - "welcome.text2": "為了完全啟動你的帳戶,我們需要驗證你註冊時所填寫的電子郵件地址。", - "welcome.text3": "管理者已核准了你的註冊申請。現在可以使用你的帳號/密碼登入。", - "welcome.cta": "請點這裡來確認你的電子郵件地址", - "invitation.text1": "%1 邀請你加入 %2", - "invitation.text2": "你的邀請將在 %1 天後過期", - "invitation.cta": "點擊這裡建立帳號", - "reset.text1": "我們收到一個重設密碼的請求,你忘記密碼了嗎?若您並未忘記密碼,請忽略這封郵件。", - "reset.text2": "要繼續重置密碼,請點擊以下連結:", - "reset.cta": "點擊這裡重置密碼", - "reset.notify.subject": "密碼修改成功", - "reset.notify.text1": "您在 %1 的密碼已修改成功", - "reset.notify.text2": "如果你未允許此動作, 請立即通知系統管理者", + "invite": "來自%1的邀請", + "greeting_no_name": "您好", + "greeting_with_name": "%1,您好", + "email.verify-your-email.subject": "請驗證你的電子信箱", + "email.verify.text1": "你的電子信箱地址已成功更改!", + "welcome.text1": "感謝您註冊 %1 帳戶!", + "welcome.text2": "在您驗證您綁定的郵件地址之後,您的帳戶才能啟用。", + "welcome.text3": "管理員批准了您的註冊申請,現在您可以登入您的帳戶了。", + "welcome.cta": "點擊這裡確認您的電子郵件地址", + "invitation.text1": "%1 邀請您加入 %2", + "invitation.text2": "您的邀請將在 %1 天後過期。", + "invitation.cta": "點擊這裡新建帳戶", + "reset.text1": "很可能是您忘記了密碼,我們收到了重設您帳戶密碼的申請。 如果您沒有申請密碼重設,請忽略這封郵件。", + "reset.text2": "如需繼續重設密碼,請點擊下面的連結:", + "reset.cta": "點擊這裡重設您的密碼", + "reset.notify.subject": "更改密碼成功", + "reset.notify.text1": "您在 %1 上的密碼已經成功修改。", + "reset.notify.text2": "如果您沒有授權此操作,請立即聯繫管理員。", "digest.latest_topics": "來自 %1 的最新主題", "digest.cta": "點擊這裡訪問 %1", - "digest.unsub.info": "本摘要依照你的訂閱設定發送給你。", - "digest.day": "日", - "digest.week": "週", + "digest.unsub.info": "根據您的訂閱設定,為您發送此摘要。", + "digest.day": "天", + "digest.week": "周", "digest.month": "月", - "digest.subject": "摘要於 %1", - "digest.title.day": "Your Daily Digest", - "digest.title.week": "Your Weekly Digest", - "digest.title.month": "Your Monthly Digest", - "notif.chat.subject": "收到來自 %1 的聊天訊息", - "notif.chat.cta": "點擊此處繼續聊天對話", - "notif.chat.unsub.info": "本聊天通知依照你的訂閱設定發送給你。", - "notif.post.unsub.info": "本文章通知依照你的訂閱設定發送給你。", - "notif.post.unsub.one-click": "Alternatively, unsubscribe from future emails like this, by clicking", - "notif.cta": "前往論壇", - "notif.cta-new-reply": "查看文章", - "notif.cta-new-chat": "查看對話", - "notif.test.short": "測試通知 ", - "notif.test.long": "這是一個通知的測試信。傳遞幫助!", - "test.text1": "這是一個測試電子郵件,用於確認你的NodeBB郵件功能是否設置正確。", - "unsub.cta": "點擊此處來更改這些設置", - "unsubscribe": "unsubscribe", - "unsub.success": "You will no longer receive emails from the %1 mailing list", - "banned.subject": "你已被禁止從%1", - "banned.text1": "user %1 已經被禁止從 %2.", - "banned.text2": "此禁令效期將至 %1 止.", - "banned.text3": "你被禁止的原因:", - "closing": "感謝!" + "digest.subject": "%1 的摘要", + "digest.title.day": "您的每日摘要", + "digest.title.week": "您的每週摘要", + "digest.title.month": "您的每月摘要", + "notif.chat.subject": "收到來自 %1 的新聊天訊息", + "notif.chat.cta": "點擊這裡繼續聊天", + "notif.chat.unsub.info": "根據您的訂閱設定,為您發送此聊天提醒。", + "notif.post.unsub.info": "根據您的訂閱設定,為您發送此回覆提醒。", + "notif.post.unsub.one-click": "或者通過點擊來取消訂閱郵件", + "notif.cta": "點擊這裡前往論壇", + "notif.cta-new-reply": "查看貼文", + "notif.cta-new-chat": "查看聊天", + "notif.test.short": "測試通知", + "notif.test.long": "這是一個測試的通知郵件。", + "test.text1": "這是一封測試郵件,用來驗證 NodeBB 的郵件設定是否正確。", + "unsub.cta": "點擊這裡修改這些設定", + "unsubscribe": "退訂", + "unsub.success": "您將不再收到來自%1郵寄名單的郵件", + "banned.subject": "您在 %1 的帳戶已被停權", + "banned.text1": "您在 %2 的帳戶 %1 已被停權。", + "banned.text2": "本次停權將在 %1 結束。", + "banned.text3": "這是您被停權的原因:", + "closing": "謝謝!" } \ No newline at end of file diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json index fb6290b1aa..04db2ca0d9 100644 --- a/public/language/zh-TW/error.json +++ b/public/language/zh-TW/error.json @@ -1,166 +1,166 @@ { - "invalid-data": "無效的資料", - "invalid-json": "無效JSON", - "not-logged-in": "你似乎還沒有登入喔!", - "account-locked": "你的帳戶暫時被鎖定!", - "search-requires-login": "需要有帳號才能搜尋 - 請先註冊或登入。", - "goback": "按上一步退回上一頁。", - "invalid-cid": "無效的類別 ID", - "invalid-tid": "無效的主題 ID", - "invalid-pid": "無效的文章 ID", - "invalid-uid": "無效的使用者 ID", - "invalid-username": "無效的使用者名稱", - "invalid-email": "無效的 Email 位址", - "invalid-fullname": "Invalid Fullname", - "invalid-location": "Invalid Location", - "invalid-birthday": "Invalid Birthday", + "invalid-data": "無效資料", + "invalid-json": "無效 JSON", + "not-logged-in": "您還沒有登入。", + "account-locked": "您的帳戶已被暫時鎖定", + "search-requires-login": "搜尋功能僅限成員使用 - 請先登入或者註冊。", + "goback": "按返回以退至前一頁", + "invalid-cid": "無效版面 ID", + "invalid-tid": "無效主題 ID", + "invalid-pid": "無效貼文 ID", + "invalid-uid": "無效使用者 ID", + "invalid-username": "無效使用者名", + "invalid-email": "無效的電子信箱", + "invalid-fullname": "無效全名", + "invalid-location": "無效位置", + "invalid-birthday": "無效生日", "invalid-title": "無效的標題", - "invalid-user-data": "無效的使用者資料", - "invalid-password": "無效的密碼", - "invalid-login-credentials": "無效的登入認證", - "invalid-username-or-password": "請指定帳號和密碼", - "invalid-search-term": "無效的搜索字詞", - "invalid-url": "網址無效", - "local-login-disabled": "已關閉非特定帳戶使用本機登入系統。", - "csrf-invalid": "我們無法讓你登入,似乎是因為連線階段已到期。請再試一次。", - "invalid-pagination-value": "無效的分頁值, 必需是至少 %1 與最多 %2", - "username-taken": "該使用者名稱已被使用", - "email-taken": "該信箱已被使用", - "email-not-confirmed": "You are unable to post until your email is confirmed, please click here to confirm your email.", - "email-not-confirmed-chat": "你需要先確認電子郵件後才能進行聊天,請點擊這裡來確認你的電子郵件。", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email. You won't be able to post or chat until your email is confirmed.", - "no-email-to-confirm": "此討論區要求確認電子郵件,請點擊這裡輸入一個電子郵件。", - "email-confirm-failed": "我們無法確認你的Email,請之後再重試。", - "confirm-email-already-sent": "確認信已寄出,請稍後 %1 分鐘才能再寄下一封。", - "sendmail-not-found": "沒有找到sendmail可執行程序,請確認它是不是已經被安裝與可讓執行NodeBB的使用者執行的", - "digest-not-enabled": "This user does not have digests enabled, or the system default is not configured to send digests", - "username-too-short": "帳號太短", - "username-too-long": "帳號太長", + "invalid-user-data": "無效使用者資料", + "invalid-password": "無效密碼", + "invalid-login-credentials": "無效登入憑證", + "invalid-username-or-password": "請確認使用者名稱和密碼", + "invalid-search-term": "無效的搜尋關鍵字", + "invalid-url": "無效的 URL", + "local-login-disabled": "已停用非管理帳戶的本地登入。", + "csrf-invalid": "可能是由於會話過期,登入失敗。請重試。", + "invalid-pagination-value": "無效的分頁數,必須介於 %1 和 %2 之間", + "username-taken": "此使用者名已被使用", + "email-taken": "此電子信箱已被使用", + "email-not-confirmed": "在您的電子信箱被確認前,您不能貼文。", + "email-not-confirmed-chat": "您的電子信箱尚未確認,無法聊天,請點擊這裡確認您的電子信箱。", + "email-not-confirmed-email-sent": "您的電子信箱尚未確認,請檢查您的收件匣。在電子信箱被確認前您不能貼文和聊天。", + "no-email-to-confirm": "本論壇需要電子信箱確認,請點擊這裡輸入電子信箱地址", + "email-confirm-failed": "我們無法確認您的電子信箱,請重試", + "confirm-email-already-sent": "確認郵件已發出,如需重新發送請等待 %1 分鐘後再試。", + "sendmail-not-found": "無法找到 sendmail 可執行檔,請確保 sendmail 已經安裝並可被運行 NodeBB 的系統帳戶執行", + "digest-not-enabled": "此使用者未開啟摘要通知,或系統設定預設不發送摘要", + "username-too-short": "使用者名太短", + "username-too-long": "使用者名太長", "password-too-long": "密碼太長", - "reset-rate-limited": "過多密碼重設請求 (比例限制)。", - "user-banned": "該使用者已被停用", - "user-banned-reason": "抱歉,此帳號已經被封鎖 (原因:%1)", - "user-banned-reason-until": "抱歉,此帳號已經被禁用至 %1 (原因:%2)", - "user-too-new": "抱歉,發表第一篇文章須要等待 %1 秒", - "blacklisted-ip": "抱歉,你的IP位址已被此論壇封鎖。若你覺得這個決定有問題,請盡速連絡管理員。", - "ban-expiry-missing": "請提供這個禁令的截止日期", - "no-category": "類別不存在", + "reset-rate-limited": "太多密碼重設請求(有頻率限制)", + "user-banned": "使用者已停權", + "user-banned-reason": "抱歉,此帳戶已經被停權 (原因:%1)", + "user-banned-reason-until": "抱歉,此帳戶已被停權,直到%1(原因:%2)", + "user-too-new": "抱歉,您需要等待 %1 秒後,才可以發文!", + "blacklisted-ip": "對不起,您的 IP 地址已被社區封鎖。如果您認為這是一個錯誤,請與管理員聯繫。", + "ban-expiry-missing": "請提供此次停權結束日期", + "no-category": "版面不存在", "no-topic": "主題不存在", - "no-post": "文章不存在", + "no-post": "貼文不存在", "no-group": "群組不存在", "no-user": "使用者不存在", - "no-teaser": "Teaser 不存在", - "no-privileges": "你沒有執行這個動作的足夠權限!", - "category-disabled": "該類別已被關閉", - "topic-locked": "該主題已被鎖定", - "post-edit-duration-expired": "您只能在文章發表後的 %1 秒後才可以編輯貼文。", - "post-edit-duration-expired-minutes": "您只能在文章發表後的 %1 分鐘後才可以編輯貼文。", - "post-edit-duration-expired-minutes-seconds": "您只能在文章發表後的 %1 分 %2 秒後才可以編輯貼文。", - "post-edit-duration-expired-hours": "您只能在文章發表後的 %1 小時後才可以編輯貼文。", - "post-edit-duration-expired-hours-minutes": "您只能在文章發表後的 %1 小時 %2分後才可以編輯貼文。", - "post-edit-duration-expired-days": "您只能在文章發表後的 %1 天後才可以編輯貼文。", - "post-edit-duration-expired-days-hours": "您只能在文章發表後的 %1 天又 %2小時後才可以編輯貼文。", - "post-delete-duration-expired": "您只能在文章發表後的 %1 秒後才可以刪文。", - "post-delete-duration-expired-minutes": "您只能在文章發表後的 %1 分鐘後才可以刪文。", - "post-delete-duration-expired-minutes-seconds": "您只能在文章發表後的 %1 分 %2秒後才可以刪文。", - "post-delete-duration-expired-hours": "您只能在文章發表後的 %1 小時後才可以刪文。", - "post-delete-duration-expired-hours-minutes": "您只能在文章發表後的 %1 小時 %2分後才可以刪文。", - "post-delete-duration-expired-days": "您只能在文章發表後的 %1 天後才可以刪文。", - "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 字元。", - "category-not-selected": "尚未選定類別。", - "too-many-posts": "在張貼 %1 秒後,你才能再張貼文章 - 請在重新張貼前等待這個時間。", - "too-many-posts-newbie": "新使用者需要直到獲得 %2 聲譽後,才能在每 %1 秒後進行張貼一篇文章 - 請在重新張貼前等待這個時間。", - "tag-too-short": "請輸入一個長一點的標籤。標籤長度不能少於 %1 字元。", - "tag-too-long": "請輸入一個短一點的標籤。標籤長度不能超過 %1 字元。", - "not-enough-tags": "標籤數量不足夠。主題需要至少 %1 標籤。", - "too-many-tags": "過多標籤數量。主題無法擁有超過 %1 個標籤。", - "still-uploading": "請等待上傳完成。", - "file-too-big": "最大允許的檔案大小是 %1 kB - 請上傳一個小一點的檔案。", - "guest-upload-disabled": "訪客無法上傳檔案", - "cors-error": "由於錯誤的校訂CORS,因此無法上傳圖片。", - "already-bookmarked": "您已經這篇文章加入書籤。", - "already-unbookmarked": "你已經將這篇文章從書籤中刪除。", - "cant-ban-other-admins": "你無法封鎖其他的管理員!", - "cant-remove-last-admin": "你是唯一的管理員。在你移除自己為管理員前,需要新增另一個使用者為管理員。", - "cant-delete-admin": "在要刪除這個帳戶前,請先移除這個帳戶的管理員權限", - "invalid-image": "無效的圖片", - "invalid-image-type": "無效的圖片類型。允許的類型:%1", - "invalid-image-extension": "無效的圖片擴充元件", - "invalid-file-type": "無效的檔案類型。允許的類型:%1", - "invalid-image-dimensions": "圖片尺寸過大。", - "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": "你的會員申請已經送出", - "group-join-disabled": "You are not able to join this group at this time", - "group-leave-disabled": "You are not able to leave this group at this time", - "post-already-deleted": "此文章已經被刪除", - "post-already-restored": "此文章已還原", - "topic-already-deleted": "此主題已經被刪除", - "topic-already-restored": "此主題已還原", - "cant-purge-main-post": "你無法清除主要的貼文,請改為刪除這個篇主題。", + "no-teaser": "主題預覽不存在", + "no-privileges": "您的權限不足以執行此操作。", + "category-disabled": "版面已停用", + "topic-locked": "主題已鎖定", + "post-edit-duration-expired": "您只能在發表後 %1 秒內修改內容", + "post-edit-duration-expired-minutes": "您只能在發表後 %1 分鐘內修改內容", + "post-edit-duration-expired-minutes-seconds": "您只能在發表後 %1 分 %2 秒內修改內容", + "post-edit-duration-expired-hours": "您只能在發表後 %1 小時後內修改內容", + "post-edit-duration-expired-hours-minutes": "您只能在發表後 %1 小時 %2 分鐘內修改內容", + "post-edit-duration-expired-days": "您只能在發表後 %1 天內修改內容", + "post-edit-duration-expired-days-hours": "您只能在發表後 %1 天 %2 小時內修改內容", + "post-delete-duration-expired": "您只能在發表後 %1 秒內刪除貼文", + "post-delete-duration-expired-minutes": "您只能在發表後 %1 分鐘內刪除貼文", + "post-delete-duration-expired-minutes-seconds": "您只能在發表發 %1 分 %2 秒內刪除貼文", + "post-delete-duration-expired-hours": "您只能在發表後 %1 小時內刪除貼文", + "post-delete-duration-expired-hours-minutes": "您只能在發表後 %1 小時 %2 分鐘內刪除貼文", + "post-delete-duration-expired-days": "您只能在發表後 %1 天內刪除貼文", + "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 個字符。", + "category-not-selected": "未選擇版面。", + "too-many-posts": "貼文需要間隔 %1 秒以上 - 請稍候再發文", + "too-many-posts-newbie": "因為您是新使用者,所以限制每隔 %1 秒才能發文一次,直到您有 %2 點聲望為止 —— 請稍候再發文", + "tag-too-short": "標籤太短,不能少於 %1 個字元", + "tag-too-long": "標籤太長,不能超過 %1 個字元", + "not-enough-tags": "沒有足夠的主題標籤。主題必須至少有 %1 個標籤", + "too-many-tags": "過多主題標籤。主題不能超過 %1 個標籤", + "still-uploading": "請等待上傳完成", + "file-too-big": "上傳檔案的大小限制為 %1 KB - 請縮減檔案大小", + "guest-upload-disabled": "訪客不允許上傳", + "cors-error": "由於CORS設定錯誤,無法上傳圖片。", + "already-bookmarked": "您已將此貼文存為了書籤", + "already-unbookmarked": "您已移除了此貼文的書籤", + "cant-ban-other-admins": "您不能封鎖其他管理員!", + "cant-remove-last-admin": "您是唯一的管理員。在刪除您的管理員權限前,請增加另一個管理員。", + "cant-delete-admin": "在刪除該帳戶之前,請先移除其管理權限。", + "invalid-image": "無效的圖檔", + "invalid-image-type": "無效的圖檔類型。允許的類型有:%1", + "invalid-image-extension": "無效的圖檔副檔名", + "invalid-file-type": "無效檔案格式,允許的格式有:%1", + "invalid-image-dimensions": "圖片尺寸太大", + "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": "已提交您的請求", + "group-join-disabled": "您目前無法加入此群組", + "group-leave-disabled": "您目前無法離開此群組", + "post-already-deleted": "此貼文已被刪除", + "post-already-restored": "此貼文已經恢復", + "topic-already-deleted": "此主題已被刪除", + "topic-already-restored": "此主題已恢復", + "cant-purge-main-post": "無法清除主貼文,請直接刪除主題", "topic-thumbnails-are-disabled": "主題縮圖已停用", - "invalid-file": "無效的檔案", - "uploads-are-disabled": "上傳功能被停用", - "signature-too-long": "抱歉,你的簽名長度不能超過 %1 字元。", - "about-me-too-long": "抱歉,關於我長度不能超過 %1 字元。", - "cant-chat-with-yourself": "你不能與自己聊天!", - "chat-restricted": "此使用者已限制了他的聊天功能。你要在他(她)關注你之後,才能跟他聊天", - "chat-disabled": "聊天系統被禁止", - "too-many-messages": "你已經送出過多的訊息,請稍等一下。", + "invalid-file": "無效檔案", + "uploads-are-disabled": "上傳已停用", + "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 messages can not be longer than %1 characters.", - "cant-edit-chat-message": "你不被允許編輯這條訊息", - "cant-remove-last-user": "你不能移除最後的使用者", - "cant-delete-chat-message": "你不被允許刪除這條訊息", - "chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting", - "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", - "chat-deleted-already": "聊天訊息已經刪除。", - "chat-restored-already": "聊天訊息已復原。", - "already-voting-for-this-post": "你已經對這個張貼投過票了", + "chat-message-too-long": "聊天訊息不能超過 %1  個字元。", + "cant-edit-chat-message": "您不能編輯這條訊息", + "cant-remove-last-user": "您不能移除這個使用者", + "cant-delete-chat-message": "您不允許刪除這條訊息", + "chat-edit-duration-expired": "您只能在發佈 %1 秒後修改聊天訊息", + "chat-delete-duration-expired": "您只能在發佈 %1 秒後刪除聊天訊息", + "chat-deleted-already": "聊天訊息已經被刪除", + "chat-restored-already": "此聊天訊息已經恢復。", + "already-voting-for-this-post": "您已讚過此貼文回覆了。", "reputation-system-disabled": "聲望系統已停用。", - "downvoting-disabled": "噓文已停用", - "not-enough-reputation-to-downvote": "你沒有足夠的聲望來噓這篇文章。", - "not-enough-reputation-to-flag": "你沒有足夠的聲望來替這篇文章增加標記。", - "not-enough-reputation-min-rep-website": "你沒有足夠的聲望來增加網站。", - "not-enough-reputation-min-rep-aboutme": "你沒有足夠的聲望來增加關於我。", - "not-enough-reputation-min-rep-signature": "你沒有足夠的聲望來增加簽名。", - "not-enough-reputation-min-rep-profile-picture": "你沒有足夠的聲望來增加個人大頭貼。", - "not-enough-reputation-min-rep-cover-picture": "你沒有足夠的聲望來增加封面圖片。", - "already-flagged": "你已經對這個張貼標記過了", - "self-vote": "你無法在自己的文章中投票。", - "reload-failed": "NodeBB重載\"%1\"時遇到了問題。 NodeBB將繼續提供現有的客戶端資源,但請你撤消重載前的動作。", + "downvoting-disabled": "倒讚已被停用", + "not-enough-reputation-to-downvote": "您的聲望不足以倒讚此貼文", + "not-enough-reputation-to-flag": "您的聲望不足以舉報此貼文", + "not-enough-reputation-min-rep-website": "您的聲望不足以設定個人網站", + "not-enough-reputation-min-rep-aboutme": "您的聲望不足以設定關於我", + "not-enough-reputation-min-rep-signature": "您的聲望不足以設定簽名檔", + "not-enough-reputation-min-rep-profile-picture": "您的聲望過低, 無法修改個人大頭貼", + "not-enough-reputation-min-rep-cover-picture": "您的聲望過低, 無法修改個人封面圖片", + "already-flagged": "您已舉報此貼文", + "self-vote": "您不能讚您自己的貼文", + "reload-failed": "重載 NodeBB 時遇到問題: \"%1\"。NodeBB 維持給已連線的客戶端服務,您應該取消重載前做的動作。", "registration-error": "註冊錯誤", - "parse-error": "解析伺服器回應時發生了某些錯誤", - "wrong-login-type-email": "請使用你的電子郵件進行登入", - "wrong-login-type-username": "請使用你的帳號進行登入", - "sso-registration-disabled": "已關閉註冊 %1 個帳號,請優先透過電子郵件地址註冊。", - "sso-multiple-association": "你無法連接多個服務的帳號至你的NodeBB帳號。請斷開連接你目前連接的帳號然後再試一次。", - "invite-maximum-met": "您已達到可邀請人數的上限 (%1人, 共%2人)。", - "no-session-found": "沒有找到登入的連線階段!", - "not-in-room": "使用者沒有在聊天室中", - "cant-kick-self": "你不能把自己從群組中踢出", - "no-users-selected": "沒有選定使用者", + "parse-error": "伺服器回應解析出錯", + "wrong-login-type-email": "請輸入您的電子信箱登入", + "wrong-login-type-username": "請輸入您的使用者名登入", + "sso-registration-disabled": "已停用通過 %1 帳戶的註冊, 請使用電子信箱地址註冊", + "sso-multiple-association": "您無法將此服務中的多個帳戶關聯到您的NodeBB賬號。請您移除連結現有帳戶並重試。", + "invite-maximum-met": "您的邀請人數超出了上限 (%1 超過了 %2)。", + "no-session-found": "未登入!", + "not-in-room": "使用者已不在聊天室中", + "cant-kick-self": "您不能把自己踢出群組", + "no-users-selected": "尚未選擇使用者", "invalid-home-page-route": "無效的首頁路徑", - "invalid-session": "會話階段錯誤", - "invalid-session-text": "看來你的登入階段已無效,或是不符合伺服器。請重新整理這個頁面。", - "no-topics-selected": "尚未選擇文章。", - "cant-move-to-same-topic": "無法移動文章到同一個主題。", - "cannot-block-self": "你無法封鎖自己。", - "cannot-block-privileged": "您無法封鎖管理者或全域管理員", - "cannot-block-guest": "訪客無法封鎖其他用戶。", - "already-blocked": "已封鎖該用戶。", - "already-unblocked": "已解除封鎖該用戶。", - "no-connection": "您的網路連線似乎有問題。" + "invalid-session": "無效 Session", + "invalid-session-text": "您的登入狀態已經失效,或者是與伺服器資訊不一致。請重載此頁面。", + "no-topics-selected": "沒有主題被選中!", + "cant-move-to-same-topic": "無法將貼文移動到相同的主題中!", + "cannot-block-self": "您不能把自己封鎖!", + "cannot-block-privileged": "您不能封鎖管理員或者超級版主", + "cannot-block-guest": "訪客無法封鎖其他使用者", + "already-blocked": "此使用者已被封鎖", + "already-unblocked": "此使用者已被取消封鎖", + "no-connection": "您的網路連線似乎有問題" } \ No newline at end of file diff --git a/public/language/zh-TW/flags.json b/public/language/zh-TW/flags.json index d614bba332..3e94a7ae7d 100644 --- a/public/language/zh-TW/flags.json +++ b/public/language/zh-TW/flags.json @@ -1,66 +1,66 @@ { - "state": "State", - "reporter": "Reporter", - "reported-at": "Reported At", + "state": "狀態", + "reporter": "舉報人", + "reported-at": "舉報於", "description": "描述", - "no-flags": "Hooray! No flags found.", - "assignee": "Assignee", + "no-flags": "帥!沒發現任何的舉報。", + "assignee": "受指派人", "update": "更新", - "updated": "更新完成", - "target-purged": "The content this flag referred to has been purged and is no longer available.", + "updated": "已更新", + "target-purged": "被舉報的內容已經被清除,不再可用。", - "graph-label": "Daily Flags", - "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": "All Content", - "filter-type-post": "文章", - "filter-type-user": "用戶", + "graph-label": "日舉報", + "quick-filters": "快速過濾器", + "filter-active": "該列中有一個或更多啟用的過濾器", + "filter-reset": "刪除過濾器", + "filters": "過濾器選項", + "filter-reporterId": "舉報者UID", + "filter-targetUid": "被舉報者 UID", + "filter-type": "舉報類型", + "filter-type-all": "所有內容", + "filter-type-post": "貼文", + "filter-type-user": "使用者", "filter-state": "狀態", - "filter-assignee": "Assignee UID", - "filter-cid": "分類", - "filter-quick-mine": "Assigned to me", - "filter-cid-all": "所有分類", - "apply-filters": "Apply Filters", + "filter-assignee": "受指派人 UID", + "filter-cid": "版面", + "filter-quick-mine": "指派給我", + "filter-cid-all": "全部版面", + "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", + "quick-links": "快速連結", + "flagged-user": "被舉報的使用者", + "view-profile": "查看個人資料", + "start-new-chat": "開始新聊天對話", + "go-to-target": "查看舉報目標", - "user-view": "查看個人資料", - "user-edit": "編輯個人資料", + "user-view": "查看資料", + "user-edit": "編輯資料", - "notes": "標記備註", + "notes": "舉報備註", "add-note": "新增備註", - "no-notes": "No shared notes.", + "no-notes": "沒有共享的備註內容。", - "history": "Flag History", - "back": "Back to Flags List", - "no-history": "No flag history.", + "history": "舉報歷史", + "back": "返回舉報列表", + "no-history": "沒有舉報歷史。", - "state-all": "All states", - "state-open": "New/Open", - "state-wip": "Work in Progress", - "state-resolved": "Resolved", + "state-all": "所有狀態", + "state-open": "新增/打開", + "state-wip": "正在處理", + "state-resolved": "已解決", "state-rejected": "已拒絕", - "no-assignee": "Not Assigned", - "note-added": "Note Added", + "no-assignee": "未指派", + "note-added": "備註已添加", - "modal-title": "回報不適當的內容", - "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": "惡意評論", - "modal-reason-other": "Other (specify below)", - "modal-reason-custom": "Reason for reporting this content...", - "modal-submit": "Submit Report", - "modal-submit-success": "Content has been flagged for moderation.", - "modal-submit-confirm": "Confirm Submission", - "modal-submit-confirm-text": "You have a custom reason specified already. Are you sure you wish to submit via quick-report?", - "modal-submit-confirm-text-help": "Submitting a quick report will overwrite any custom reasons defined." + "modal-title": "舉報不適內容", + "modal-body": "請選擇或者輸入您舉報 %1%2 的原因以便版主進行審核。", + "modal-reason-spam": "垃圾訊息", + "modal-reason-offensive": "人身攻擊", + "modal-reason-other": "其它(請在下方指定)", + "modal-reason-custom": "舉報此內容的理由……", + "modal-submit": "提交舉報", + "modal-submit-success": "已舉報此內容。", + "modal-submit-confirm": "確認提交", + "modal-submit-confirm-text": "您已經指定了一個自訂原因。 您希望通過快速報告提交嗎?", + "modal-submit-confirm-text-help": "提交快速報告將覆寫任何已定義的自訂原因。" } \ No newline at end of file diff --git a/public/language/zh-TW/global.json b/public/language/zh-TW/global.json index 724d2c7270..a6555b830a 100644 --- a/public/language/zh-TW/global.json +++ b/public/language/zh-TW/global.json @@ -2,31 +2,31 @@ "home": "首頁", "search": "搜尋", "buttons.close": "關閉", - "403.title": "禁止存取", - "403.message": "你沒有該頁面的存取權限。", - "403.login": "或許是你應該 試著登入?", - "404.title": "無法找到該頁", - "404.message": "你所查找的頁面並不存在。返回首頁。", + "403.title": "禁止訪問", + "403.message": "您似乎沒有訪問此頁面的權限。", + "403.login": "或許您應該先 登入試試?", + "404.title": "未找到", + "404.message": "您訪問的頁面不存在。返回首頁。", "500.title": "內部錯誤", - "500.message": "糟糕! 看來是不知道哪裡出錯了!", - "400.title": "錯誤要求", - "400.message": "看起來這個連結是有問題的,請點擊兩次然後重新嘗試。要不然,回到首頁。", + "500.message": "哎呀!看來是哪裡出錯了!", + "400.title": "錯誤的請求", + "400.message": "看起來這個連結的格式不正確,請再次檢查並重試。或者返回首頁。", "register": "註冊", "login": "登入", - "please_log_in": "請先登入", + "please_log_in": "請登入", "logout": "登出", - "posting_restriction_info": "張貼文章目前只限註冊會員,點擊此處進行登入。", - "welcome_back": "歡迎回來!", - "you_have_successfully_logged_in": "你已成功登入!", - "save_changes": "儲存變更", + "posting_restriction_info": "僅限已註冊成員發文,點這裡登入。", + "welcome_back": "歡迎回來", + "you_have_successfully_logged_in": "您已成功登入", + "save_changes": "儲存更改", "save": "儲存", "close": "關閉", "pagination": "分頁", - "pagination.out_of": "%1 於 %2", + "pagination.out_of": "%1 / %2", "pagination.enter_index": "輸入索引", "header.admin": "管理", - "header.categories": "類別", - "header.recent": "最近", + "header.categories": "版面", + "header.recent": "最新", "header.unread": "未讀", "header.tags": "標籤", "header.popular": "熱門", @@ -35,81 +35,81 @@ "header.chats": "聊天", "header.notifications": "通知", "header.search": "搜尋", - "header.profile": "個人資料", - "header.navigation": "導覽", - "notifications.loading": "通知載入中", - "chats.loading": "聊天載入中", - "motd.welcome": "歡迎來到 NodeBB,一個未來的討論平台。", + "header.profile": "個人檔案", + "header.navigation": "導航", + "notifications.loading": "正在載入通知", + "chats.loading": "正在載入聊天", + "motd.welcome": "歡迎來到 NodeBB,未來的社區論壇平臺。", "previouspage": "上一頁", "nextpage": "下一頁", "alert.success": "成功", "alert.error": "錯誤", - "alert.banned": "封鎖", - "alert.banned.message": "你剛被封鎖,將會被登出!", - "alert.unfollow": "你已不再跟隨 %1!", - "alert.follow": "你正在跟隨 %1!", + "alert.banned": "已停權", + "alert.banned.message": "您剛剛被停權了,現在您將登出網站。", + "alert.unfollow": "您已取消追隨 %1!", + "alert.follow": "您已追隨 %1!", "users": "使用者", "topics": "主題", - "posts": "張貼", - "best": "最棒", - "votes": "Votes", - "upvoters": "正向投票", - "upvoted": "正向投票", - "downvoters": "負向投票", - "downvoted": "負向投票", + "posts": "貼文", + "best": "最佳", + "votes": "評價", + "upvoters": "點贊的人", + "upvoted": "點讚", + "downvoters": "倒讚的人", + "downvoted": "倒讚", "views": "瀏覽", "reputation": "聲望", - "lastpost": "Last post", - "firstpost": "First post", + "lastpost": "上一個貼文", + "firstpost": "第一個貼文", "read_more": "閱讀更多", "more": "更多", - "posted_ago_by_guest": "訪客張貼 %1", - "posted_ago_by": "%2 張貼 %1 ", - "posted_ago": "張貼 %1", - "posted_in": "張貼在 %1", - "posted_in_by": "張貼於 %1 由 %2", - "posted_in_ago": "張貼在 %1 %2", - "posted_in_ago_by": "由 %3 張貼在 %1 %2 ", - "user_posted_ago": "%1 張貼 %2", - "guest_posted_ago": "訪客張貼 %1", - "last_edited_by": "最後編輯由 %1", - "norecentposts": "沒新張貼文", - "norecenttopics": "沒新主題", - "recentposts": "最近的張貼文", - "recentips": "最近登入的 IP 位址", - "moderator_tools": "Moderator Tools", + "posted_ago_by_guest": "訪客發佈於 %1", + "posted_ago_by": "%2 發佈於 %1", + "posted_ago": "發佈於 %1", + "posted_in": "發佈在 %1", + "posted_in_by": "%2 發佈於 %1", + "posted_in_ago": "於 %2 發佈到 %1 版", + "posted_in_ago_by": "%3 於 %1 發佈到 %2", + "user_posted_ago": "%1 發佈於 %2", + "guest_posted_ago": "訪客發佈於 %1", + "last_edited_by": "最後由 %1 編輯", + "norecentposts": "暫無新貼文", + "norecenttopics": "暫無新主題", + "recentposts": "新貼文", + "recentips": "最近登入的 IP", + "moderator_tools": "版主工具", "online": "線上", "away": "離開", - "dnd": "勿打擾", - "invisible": "隱形", + "dnd": "請勿打擾", + "invisible": "隱身", "offline": "離線", - "email": "Email", + "email": "電子信箱", "language": "語言", "guest": "訪客", "guests": "訪客", - "former_user": "A Former User", - "updated.title": "討論區已更新", - "updated.message": "這個討論區最近已更新至最新的版本。點擊此處來重整這個頁面。", + "former_user": "舊使用者", + "updated.title": "論壇已更新", + "updated.message": "論壇已更新。請點這裡重載頁面。", "privacy": "隱私", - "follow": "跟隨", - "unfollow": "取消跟隨", + "follow": "追隨", + "unfollow": "取消追隨", "delete_all": "全部刪除", "map": "地圖", - "sessions": "登入連線階段", - "ip_address": "IP地址", - "enter_page_number": "輸入頁碼", + "sessions": "已登入的會話", + "ip_address": "IP 地址", + "enter_page_number": "輸入頁號", "upload_file": "上傳檔案", "upload": "上傳", - "uploads": "Uploads", - "allowed-file-types": "允許的檔案類型是 %1", - "unsaved-changes": "你還沒有儲存更動。你確定想要離開這個頁面?", - "reconnecting-message": "看起來你的連線到 %1 已經遺失,請稍等一下我們嘗試重新連線。", + "uploads": "上傳", + "allowed-file-types": "允許的檔案類型有 %1", + "unsaved-changes": "您有未儲存的更改,您確定您要離開嗎?", + "reconnecting-message": "與 %1 的連線中斷,我們正在嘗試重連,請耐心等待", "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", - "edited": "Edited", - "disabled": "Disabled", - "select": "Select", - "user-search-prompt": "Type something here to find users..." + "cookies.message": "此網站使用 Cookies 以確保您在我們網站的最佳體驗。", + "cookies.accept": "知道了!", + "cookies.learn_more": "瞭解更多", + "edited": "已編輯", + "disabled": "停用", + "select": "選擇", + "user-search-prompt": "輸入以搜尋使用者" } \ No newline at end of file diff --git a/public/language/zh-TW/groups.json b/public/language/zh-TW/groups.json index 854d6c4879..9681dfeb14 100644 --- a/public/language/zh-TW/groups.json +++ b/public/language/zh-TW/groups.json @@ -1,63 +1,63 @@ { "groups": "群組", - "view_group": "查看群組", - "owner": "群組擁有者", - "new_group": "建立新群組", - "no_groups_found": "這裡看不到任何群組", - "pending.accept": "接受", + "view_group": "檢視群組", + "owner": "群組所有者", + "new_group": "新增群組", + "no_groups_found": "尚無群組訊息", + "pending.accept": "同意", "pending.reject": "拒絕", - "pending.accept_all": "同意所有", - "pending.reject_all": "拒絕所有", - "pending.none": "目前沒有等待中的會員", - "invited.none": "目前沒有邀請的會員", - "invited.uninvite": "撤銷邀請", - "invited.search": "搜尋要邀請加入這個群組的使用者", - "invited.notification_title": "你已被邀請加入%1", - "request.notification_title": "群組會員要求,來自%1", - "request.notification_text": "%1已經要求成為%2群組的會員", + "pending.accept_all": "全部同意", + "pending.reject_all": "全部拒絕", + "pending.none": "暫時沒有待加入的成員", + "invited.none": "暫時沒有接受邀請的成員", + "invited.uninvite": "取消邀請", + "invited.search": "選擇使用者加入群組", + "invited.notification_title": "您已被邀請加入 %1", + "request.notification_title": "來自 %1 的群組成員請求", + "request.notification_text": "%1 已被邀請加入 %2", "cover-save": "儲存", - "cover-saving": "儲存中", - "details.title": "群組詳細信息", + "cover-saving": "正在儲存", + "details.title": "群組訊息", "details.members": "成員列表", - "details.pending": "待審成員", - "details.invited": "邀請會員", - "details.has_no_posts": "這個群組的成員還未發出任何帖子。", - "details.latest_posts": "最新文章", - "details.private": "私人", - "details.disableJoinRequests": "禁止加入要求", - "details.disableLeave": "Disallow users from leaving the group", - "details.grant": "准許/撤銷 所有權", - "details.kick": "剔除", - "details.kick_confirm": "Are you sure you want to remove this member from the group?", - "details.add-member": "Add Member", - "details.owner_options": "群組管理員", - "details.group_name": "群組名稱", - "details.member_count": "成員數", - "details.creation_date": "建立日期", - "details.description": "簡介", + "details.pending": "待加入成員", + "details.invited": "已邀請成員", + "details.has_no_posts": "此群組的成員尚未發表任何貼文。", + "details.latest_posts": "最新貼文", + "details.private": "私有", + "details.disableJoinRequests": "禁用申請加入群組", + "details.disableLeave": "禁用使用者離開群組", + "details.grant": "准許/撤銷管理權", + "details.kick": "踢出群組", + "details.kick_confirm": "您確定要將此成員從群組中移除嗎?", + "details.add-member": "新增成員", + "details.owner_options": "群組管理", + "details.group_name": "群組名", + "details.member_count": "群組成員數", + "details.creation_date": "建立時間", + "details.description": "描述", "details.badge_preview": "徽章預覽", - "details.change_icon": "變更圖標", - "details.change_label_colour": "Change Label Colour", - "details.change_text_colour": "Change Text Colour", - "details.badge_text": "徽章字串", - "details.userTitleEnabled": "顯示徽章", - "details.private_help": "如果開啟,加入群組需要經過群組擁有者批準", + "details.change_icon": "更改圖示", + "details.change_label_colour": "更改標籤顏色", + "details.change_text_colour": "更改文字顏色", + "details.badge_text": "徽章文字", + "details.userTitleEnabled": "顯示組內稱號", + "details.private_help": "啟用此選項後,加入群組需要組長審核。", "details.hidden": "隱藏", - "details.hidden_help": "如果開啟的話,群組將不會在群組列表中被看到,而且使用者將需要手動邀請", + "details.hidden_help": "啟用此選項後,群組將不在群組列表中展現,成員只能通過邀請加入。", "details.delete_group": "刪除群組", - "details.private_system_help": "私有群組在系統層級被禁用,這個選項沒有任何作用", - "event.updated": "群組詳細訊息已被更新", - "event.deleted": "此 \"%1\" 群組已被刪除了", - "membership.accept-invitation": "同意邀請", - "membership.accept.notification_title": "You are now a member of %1", - "membership.invitation-pending": "邀請等待中", + "details.private_system_help": "系統禁用了私有群組,這個選項不起任何作用", + "event.updated": "群組訊息已更新", + "event.deleted": "群組 \"%1\" 已被刪除", + "membership.accept-invitation": "接受邀請", + "membership.accept.notification_title": "你現在是 %1的成員了", + "membership.invitation-pending": "邀請中", "membership.join-group": "加入群組", - "membership.leave-group": "離開群組", - "membership.leave.notification_title": "%1 has left group %2", + "membership.leave-group": "退出群組", + "membership.leave.notification_title": "%1 退出了群組:%2", "membership.reject": "拒絕", - "new-group.group_name": "群組名稱:", - "upload-group-cover": "上傳群組封面圖", - "bulk-invite-instructions": "Enter a list of comma separated usernames to invite to this group", - "bulk-invite": "Bulk Invite", - "remove_group_cover_confirm": "Are you sure you want to remove the cover picture?" + "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-TW/login.json b/public/language/zh-TW/login.json index bd3cf262d9..6683f8f0e4 100644 --- a/public/language/zh-TW/login.json +++ b/public/language/zh-TW/login.json @@ -1,12 +1,12 @@ { - "username-email": "帳號 / 電子郵件", - "username": "帳號", - "email": "電子郵件", - "remember_me": "記住我?", + "username-email": "使用者名 / 電子信箱", + "username": "使用者名", + "email": "電子信箱", + "remember_me": "保持登入?", "forgot_password": "忘記密碼?", - "alternative_logins": "其他登入方式", + "alternative_logins": "使用合作網站帳戶登錄", "failed_login_attempt": "登入失敗", - "login_successful": "你已成功登入!", - "dont_have_account": "還沒有帳號?", - "logged-out-due-to-inactivity": "你已登出管理控制台,因為一段時間沒有任何動作。" + "login_successful": "您已成功登入!", + "dont_have_account": "沒有帳戶?", + "logged-out-due-to-inactivity": "由於長時間沒有活動,您的帳戶已被管理員從後台登出" } \ No newline at end of file diff --git a/public/language/zh-TW/modules.json b/public/language/zh-TW/modules.json index cb21142a29..5c3b3c61cd 100644 --- a/public/language/zh-TW/modules.json +++ b/public/language/zh-TW/modules.json @@ -1,66 +1,67 @@ { - "chat.chatting_with": "聊天對象", - "chat.placeholder": "在這裡輸入訊息,按 Enter 發送", + "chat.chatting_with": "與聊天", + "chat.placeholder": "在這裡輸入聊天訊息,按返回鍵發送", "chat.send": "發送", "chat.no_active": "暫無聊天", - "chat.user_typing": "%1 正在輸入中...", - "chat.user_has_messaged_you": "%1 傳送訊息給你", - "chat.see_all": "顯示全部聊天", - "chat.mark_all_read": "所有訊息標為已讀", - "chat.no-messages": "請選擇收件人來查看聊天記錄", - "chat.no-users-in-room": "沒有使用者在聊天室中", - "chat.recent-chats": "最近的聊天記錄", - "chat.contacts": "通訊錄", - "chat.message-history": "消息記錄", - "chat.options": "聊天選項", - "chat.pop-out": "彈出聊天室", + "chat.user_typing": "%1 正在輸入……", + "chat.user_has_messaged_you": "%1 向您發送了訊息。", + "chat.see_all": "查看所有對話", + "chat.mark_all_read": "將所有聊天標為已讀", + "chat.no-messages": "請選擇接收人,以查看聊天訊息紀錄", + "chat.no-users-in-room": "此聊天室中沒有使用者", + "chat.recent-chats": "最近聊天", + "chat.contacts": "聯絡人", + "chat.message-history": "訊息紀錄", + "chat.message-deleted": "訊息已刪除", + "chat.options": "聊天設定", + "chat.pop-out": "彈出聊天視窗", "chat.minimize": "最小化", "chat.maximize": "最大化", - "chat.seven_days": "7日", - "chat.thirty_days": "30日", + "chat.seven_days": "7天", + "chat.thirty_days": "30天", "chat.three_months": "3個月", - "chat.delete_message_confirm": "你確定要刪除這個訊息?", - "chat.retrieving-users": "收回使用者...", + "chat.delete_message_confirm": "您確定刪除此訊息嗎?", + "chat.retrieving-users": "搜尋使用者", "chat.manage-room": "管理聊天室", - "chat.add-user-help": "Search for users here. When selected, the user will be added to the chat. The new user will not be able to see chat messages written before they were added to the conversation. Only room owners () may remove users from chat rooms.", - "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?", - "chat.rename-room": "重新命名聊天室", - "chat.rename-placeholder": "在這裡輸入您的聊天室名稱", - "chat.rename-help": "The room name set here will be viewable by all participants in the room.", + "chat.add-user-help": "在這裡搜尋更多使用者。選中之後加入到聊天中,新使用者在加入聊天之前看不到聊天訊息。只有聊天室所有者()可以從聊天室中移除使用者。", + "chat.confirm-chat-with-dnd-user": "該使用者已將其狀態設置為 DnD(請勿打擾)。 您仍希望與其聊天嗎?", + "chat.rename-room": "重新命名房間", + "chat.rename-placeholder": "在這裡輸入房間名字", + "chat.rename-help": "這裡設定的房間名字能夠被房間內所有人都看到。", "chat.leave": "離開聊天室", - "chat.leave-prompt": "確定要離開聊天室嗎?", - "chat.leave-help": "Leaving this chat will remove you from future correspondence in this chat. If you are re-added in the future, you will not see any chat history from prior to your re-joining.", - "chat.in-room": "在這間聊天室", - "chat.kick": "踢", - "chat.show-ip": "顯示IP", - "chat.owner": "聊天室主人", - "chat.system.user-join": "%1 has joined the room", - "chat.system.user-leave": "%1 has left the room", - "chat.system.room-rename": "%2 has renamed this room: %1", + "chat.leave-prompt": "您確定要離開聊天室?", + "chat.leave-help": "離開此聊天會將您在聊天中的未接收的訊息移除。您在重新加入之後不會看到任何聊天記錄", + "chat.in-room": "在此房間", + "chat.kick": "踢出", + "chat.show-ip": "顯示 IP", + "chat.owner": "房間所有者", + "chat.system.user-join": "%1 加入了房間", + "chat.system.user-leave": "%1 離開了房間", + "chat.system.room-rename": "%2 更改房間名為:%1", "composer.compose": "撰寫", "composer.show_preview": "顯示預覽", "composer.hide_preview": "隱藏預覽", - "composer.user_said_in": "%1 在 %2 裡說:", + "composer.user_said_in": "%1 在 %2 中說:", "composer.user_said": "%1 說:", - "composer.discard": "你確定要放棄這帖子嗎?", - "composer.submit_and_lock": "提交然後鎖定", - "composer.toggle_dropdown": "切換下拉選單", - "composer.uploading": "上傳中 %1", + "composer.discard": "確定想要取消此貼文?", + "composer.submit_and_lock": "提交並鎖定", + "composer.toggle_dropdown": "標為 Dropdown", + "composer.uploading": "正在上傳 %1", "composer.formatting.bold": "粗體", "composer.formatting.italic": "斜體", - "composer.formatting.list": "列表項目", + "composer.formatting.list": "清單", "composer.formatting.strikethrough": "刪除線", - "composer.formatting.code": "code", + "composer.formatting.code": "程式碼", "composer.formatting.link": "連結", "composer.formatting.picture": "圖片", "composer.upload-picture": "上傳圖片", "composer.upload-file": "上傳檔案", - "composer.zen_mode": "禪(Zen)模式", - "composer.select_category": "選擇一個類別", - "bootbox.ok": "好", + "composer.zen_mode": "無干擾模式", + "composer.select_category": "選擇一個版面", + "bootbox.ok": "確認", "bootbox.cancel": "取消", "bootbox.confirm": "確認", - "cover.dragging_title": "封面照片位置", - "cover.dragging_message": "拖拉封面照片到想要的位置,然後按下\"儲存\"", - "cover.saved": "封面照片與位置已儲存" + "cover.dragging_title": "設定封面照片位置", + "cover.dragging_message": "拖拽封面照片到期望的位置,然後點擊“儲存”", + "cover.saved": "封面照片和位置已儲存" } \ No newline at end of file diff --git a/public/language/zh-TW/notifications.json b/public/language/zh-TW/notifications.json index ff6614514e..1eef215d99 100644 --- a/public/language/zh-TW/notifications.json +++ b/public/language/zh-TW/notifications.json @@ -1,64 +1,64 @@ { "title": "通知", - "no_notifs": "沒有新消息", - "see_all": "觀看所有通知", - "mark_all_read": "所有訊息設為已讀", + "no_notifs": "您沒有新的通知", + "see_all": "查看全部通知", + "mark_all_read": "標記全部為已讀", "back_to_home": "返回 %1", "outgoing_link": "站外連結", - "outgoing_link_message": "你現在離開了 %1", + "outgoing_link_message": "您正在離開 %1", "continue_to": "繼續前往 %1", "return_to": "返回 %1", - "new_notification": "你有新的通知", - "you_have_unread_notifications": "你有未讀的通知。", - "all": "全部", + "new_notification": "您有一個新的通知", + "you_have_unread_notifications": "您有未讀的通知。", + "all": "所有", "topics": "主題", "replies": "回覆", "chat": "聊天", "follows": "關注", - "upvote": "按讚", - "new-flags": "新的標記", - "my-flags": "指定給我的標記", - "bans": "封鎖", + "upvote": "點讚", + "new-flags": "新舉報", + "my-flags": "指派舉報給我", + "bans": "停權", "new_message_from": "來自 %1 的新訊息", - "upvoted_your_post_in": "%1 對你在 %2的張貼文投了正向票。", - "upvoted_your_post_in_dual": "%1%2 已經對你在%3的張貼作正向投票。", - "upvoted_your_post_in_multiple": "%1 與 %2 其他人已經對你在%3正向投票。", - "moved_your_post": "%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", - "user_flagged_user": "%1 標記的使用者個人檔案(%2)。", - "user_flagged_user_dual": "%1%2 標記一個使用者個人檔案 (%3)", - "user_flagged_user_multiple": "%1 和 %2 其他標記的使用者個人檔案 (%3)", - "user_posted_to": "%1 發布一個回覆給: %2", - "user_posted_to_dual": "%1%2 已經張貼回覆到: %3", - "user_posted_to_multiple": "%1 與 %2 其他人已經張貼回覆到: %3", - "user_posted_topic": "%1 發布了一個新的主題: %2", - "user_started_following_you": "%1 開始關注你。", - "user_started_following_you_dual": "%1%2 開始跟隨你。", - "user_started_following_you_multiple": "%1 與 %2 其他的開始跟隨你。", - "new_register": "%1傳送了註冊要求。", - "new_register_multiple": "目前有 %1 個註冊要求等待審核中。", - "flag_assigned_to_you": "標記 %1 已經被指派給你", - "post_awaiting_review": "帖子正等待審核", - "email-confirmed": "已確認電子郵件", - "email-confirmed-message": "感謝你驗證電子郵件。你的帳戶現已完整的啟動。", - "email-confirm-error-message": "驗證你的電子郵件地址時發生問題。也許是啟動碼無效或是已過期。", - "email-confirm-sent": "已發送確認電子郵件。", + "upvoted_your_post_in": "%1%2 點讚了您的貼文。", + "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", + "user_flagged_post_in": "%1%2 舉報了一個貼文", + "user_flagged_post_in_dual": "%1%2%3 舉報了一個貼文", + "user_flagged_post_in_multiple": "%1 和 %2 個其他人在 %3 舉報了一個貼文", + "user_flagged_user": "%1 舉報了 (%2) 的使用者資料", + "user_flagged_user_dual": "%1%2 舉報了 (%3) 的使用者資料", + "user_flagged_user_multiple": "%1 和其他 %2 人舉報了 (%3) 的使用者資料", + "user_posted_to": "%1 回覆了:%2", + "user_posted_to_dual": "%1%2 回覆了: %3", + "user_posted_to_multiple": "%1 和 %2 個其他人回覆了: %3", + "user_posted_topic": "%1 發表了新主題:%2", + "user_started_following_you": "%1追隨了您。", + "user_started_following_you_dual": "%1%2 追隨了您。", + "user_started_following_you_multiple": "%1 和 %2 個其他人追隨了您。", + "new_register": "%1 發出了註冊請求", + "new_register_multiple": "有 %1 個註冊申請等待批准。", + "flag_assigned_to_you": "舉報 %1 已經被指派給您", + "post_awaiting_review": "請求查驗貼文", + "email-confirmed": "電子信箱已確認", + "email-confirmed-message": "感謝您驗證您的電子信箱。您的帳戶現已完全啟用。", + "email-confirm-error-message": "驗證的您電子信箱地址時出現了問題。可能是因為驗證碼無效或已過期。", + "email-confirm-sent": "確認郵件已發送。", "none": "不通知", - "notification_only": "通知訊息欄", - "email_only": "電郵", - "notification_and_email": "通知訊息欄及電郵", - "notificationType_upvote": "當有人對你的文章按讚。", - "notificationType_new-topic": "當你追蹤的人發表一篇主題。", - "notificationType_new-reply": "當一則新回覆發表在你關注的主題", - "notificationType_follow": "當有用戶開始關注你", - "notificationType_new-chat": "當你收到聊天訊息", - "notificationType_group-invite": "當你受到群組邀請", - "notificationType_group-request-membership": "當用戶請求加入你管理的群組", - "notificationType_new-register": "當有人被加入註冊隊列", - "notificationType_post-queue": "當一則新的文章在隊列", - "notificationType_new-post-flag": "當一則文章被標記", - "notificationType_new-user-flag": "當一位使用者被標記" + "notification_only": "頁面提醒", + "email_only": "電子郵件", + "notification_and_email": "頁面以及電郵", + "notificationType_upvote": "當有人點贊了我的貼文時", + "notificationType_new-topic": "當有人回覆我的貼文時", + "notificationType_new-reply": "當您正在查看的主題中有新回覆時", + "notificationType_follow": "當有人追隨您時", + "notificationType_new-chat": "當您收到聊天訊息時", + "notificationType_group-invite": "當您收到群組邀請時", + "notificationType_group-request-membership": "當有人請求加入您擁有的群組時", + "notificationType_new-register": "當有註冊申請待審核時", + "notificationType_post-queue": "當有新貼文等待審核時", + "notificationType_new-post-flag": "當有新的貼文舉報時", + "notificationType_new-user-flag": "當有新的使用者資料舉報時" } \ No newline at end of file diff --git a/public/language/zh-TW/pages.json b/public/language/zh-TW/pages.json index c6893768fc..a60198c98d 100644 --- a/public/language/zh-TW/pages.json +++ b/public/language/zh-TW/pages.json @@ -1,64 +1,64 @@ { "home": "首頁", - "unread": "未讀的主題", - "popular-day": "今天受歡迎的主題", - "popular-week": "本週受歡迎的主題", - "popular-month": "本月受歡迎的主題", - "popular-alltime": "所有時間受歡迎的主題", - "recent": "近期的主題", - "top-day": "今天最受投票歡迎的主題", - "top-week": "這一星期最受投票歡迎的主題", - "top-month": "這一個月最受投票歡迎的主題", - "top-alltime": "最受投票歡迎的主題", - "moderator-tools": "版主工具箱", - "flagged-content": "標記的內容", - "ip-blacklist": "IP禁止列表", - "post-queue": "文章隊列", + "unread": "未讀", + "popular-day": "今日熱門主題", + "popular-week": "本週熱門主題", + "popular-month": "當月熱門主題", + "popular-alltime": "熱門主題", + "recent": "最新主題", + "top-day": "今日點贊最高的主題", + "top-week": "本週點贊最高的主題", + "top-month": "本月點贊最高的主題", + "top-alltime": "點贊最高的主題", + "moderator-tools": "版主工具", + "flagged-content": "舉報管理", + "ip-blacklist": "IP 黑名單", + "post-queue": "貼文隊列", "users/online": "線上使用者", - "users/latest": "最近使用者", - "users/sort-posts": "最多張貼的使用者", - "users/sort-reputation": "最多聲譽的使用者", - "users/banned": "已封鎖使用者", - "users/most-flags": "最多標註的使用者", + "users/latest": "最新使用者", + "users/sort-posts": "發文最多的使用者", + "users/sort-reputation": "聲望最高的使用者", + "users/banned": "被停權的使用者", + "users/most-flags": "被舉報次數最多的使用者", "users/search": "使用者搜尋", - "notifications": "新訊息通知", + "notifications": "通知", "tags": "標籤", - "tag": "主題標籤 - "%1"", - "register": "註冊帳號", + "tag": "標籤為\"%1\"的主題", + "register": "註冊帳戶", "registration-complete": "註冊完成", - "login": "登入帳號", - "reset": "重設你的帳號密碼", - "categories": "類別", + "login": "登入帳戶", + "reset": "重設帳戶密碼", + "categories": "版面", "groups": "群組", - "group": "%1 群組", + "group": "%1 的群組", "chats": "聊天", "chat": "與 %1 聊天", - "flags": "標記", - "flag-details": "標記 %1 細節", - "account/edit": "編輯 \"%1\"", - "account/edit/password": "編輯 \"%1\" 的密碼", - "account/edit/username": "編輯\"%1\"的帳號", - "account/edit/email": "編輯\"%1\"的電子郵件", + "flags": "舉報", + "flag-details": "舉報 %1 詳情", + "account/edit": "正在編輯 \"%1\"", + "account/edit/password": "正在編輯 \"%1\" 的密碼", + "account/edit/username": "正在編輯 \"%1\" 的使用者名", + "account/edit/email": "正在編輯 \"%1\" 的電子信箱", "account/info": "帳戶資訊", - "account/following": "使用者 %1 跟隨", - "account/followers": "跟隨 %1 的使用者", - "account/posts": "由 %1 發表的張貼", - "account/latest-posts": "Latest posts made by %1", - "account/topics": "由 %1 建立的主題", + "account/following": "%1 關注", + "account/followers": "關注 %1 的人", + "account/posts": "%1 發佈的貼文", + "account/latest-posts": "%1 發佈的最新貼文", + "account/topics": "%1 建立的主題", "account/groups": "%1 的群組", - "account/watched_categories": "%1 觀看的類別", - "account/bookmarks": "%1's 已添加書籤的帖子", + "account/watched_categories": "%1 關注的版面", + "account/bookmarks": "%1 收藏的貼文", "account/settings": "使用者設定", - "account/watched": "%1 所觀看的主題", - "account/ignored": "被 %1 忽略的主題", - "account/upvoted": "%1 所正向投票的張貼", - "account/downvoted": "%1 所負向投票的張貼", - "account/best": "由 %1 建立的最佳張貼文", - "account/blocks": "為了 %1 封鎖的使用者", - "account/uploads": "由 %1 上傳", - "account/sessions": "登入階段", - "confirm": "已確認電子郵件", - "maintenance.text": "目前 %1 正在進行維修。請稍後再來。", - "maintenance.messageIntro": "此外,管理員有以下訊息:", - "throttled.text": "%1 目前無法提供,是因為使用量過度。請之後再回來操作。" + "account/watched": "主題已被 %1 關注", + "account/ignored": "主題已被 %1 忽略", + "account/upvoted": "貼文被 %1 點贊", + "account/downvoted": "貼文被 %1 倒讚", + "account/best": "%1 發佈的最佳貼文", + "account/blocks": "%1 封鎖的使用者", + "account/uploads": "%1 上傳的檔案", + "account/sessions": "已登入的會話", + "confirm": "電子信箱已確認", + "maintenance.text": "%1 正在進行維護。請稍後再來。", + "maintenance.messageIntro": "此外,管理員留下的訊息:", + "throttled.text": "%1 因負荷超載暫不可用。請稍後再來。" } \ No newline at end of file diff --git a/public/language/zh-TW/recent.json b/public/language/zh-TW/recent.json index 6076b0acb6..7045c6752d 100644 --- a/public/language/zh-TW/recent.json +++ b/public/language/zh-TW/recent.json @@ -1,19 +1,19 @@ { - "title": "最近", + "title": "最新", "day": "日", "week": "周", "month": "月", "year": "年", - "alltime": "所有時間", - "no_recent_topics": "最近沒有新的主題。", - "no_popular_topics": "最近沒有受歡迎的主題。", - "there-is-a-new-topic": "有一篇新主題", - "there-is-a-new-topic-and-a-new-post": "有一篇新主題與一篇新張貼", - "there-is-a-new-topic-and-new-posts": "有一篇新主題與 %1 篇新張貼", - "there-are-new-topics": "有 %1 篇新主題", - "there-are-new-topics-and-a-new-post": "有 %1 篇新主題與一篇新張貼", - "there-are-new-topics-and-new-posts": "有 %1 篇新主題與 %2 篇新張貼", - "there-is-a-new-post": "有一篇新張貼", - "there-are-new-posts": "有 %1 篇新張貼", - "click-here-to-reload": "點擊這裡進行重整。" + "alltime": "總計", + "no_recent_topics": "暫無主題。", + "no_popular_topics": "暫無熱門主題。", + "there-is-a-new-topic": "共計 1 個新主題。", + "there-is-a-new-topic-and-a-new-post": "共計 1 個新主題和 1 個新回覆。", + "there-is-a-new-topic-and-new-posts": "共計 1 個新主題和 %1 個新回覆。", + "there-are-new-topics": "共計 %1 個新主題。", + "there-are-new-topics-and-a-new-post": "共計 %1 個新主題和 1 個新回覆。", + "there-are-new-topics-and-new-posts": "共計 %1 個新主題和 %2 個新回覆。", + "there-is-a-new-post": "共計 1 個新回覆。", + "there-are-new-posts": "共計 %1 個新回覆。", + "click-here-to-reload": "點擊這裡重新載入。" } \ No newline at end of file diff --git a/public/language/zh-TW/register.json b/public/language/zh-TW/register.json index 74a92da001..67d7cd198e 100644 --- a/public/language/zh-TW/register.json +++ b/public/language/zh-TW/register.json @@ -1,26 +1,26 @@ { "register": "註冊", "cancel_registration": "取消註冊", - "help.email": "在預設情況下,你的電子郵件地址不會被公開。", - "help.username_restrictions": "獨立的帳號由 %1 到 %2 個字元組成。其他人可以通過 @帳號 提及你。", - "help.minimum_password_length": "密碼必須至少包含 %1 個字元。", - "email_address": "Email", - "email_address_placeholder": "輸入電子郵件地址", - "username": "帳號", - "username_placeholder": "輸入帳號", + "help.email": "預設情況下,您的電子信箱不會公開。", + "help.username_restrictions": "全站唯一的登入名稱,長度 %1 到 %2 個字元。其他人可以使用 @使用者名 提及您。", + "help.minimum_password_length": "您的密碼長度必須不少於 %1 個字元。", + "email_address": "電子信箱地址", + "email_address_placeholder": "輸入電子信箱地址", + "username": "使用者名", + "username_placeholder": "輸入使用者名", "password": "密碼", "password_placeholder": "輸入密碼", "confirm_password": "確認密碼", "confirm_password_placeholder": "再次輸入密碼", - "register_now_button": "現在註冊", - "alternative_registration": "其他註冊方式", + "register_now_button": "立即註冊", + "alternative_registration": "其它方式註冊", "terms_of_use": "使用條款", - "agree_to_terms_of_use": "同意遵守使用條款", - "terms_of_use_error": "你需要同意使用條款", - "registration-added-to-queue": "你的註冊已經被加入到審核序列中。你將會在管理者批準後收到一封電子郵件。", - "interstitial.intro": "我們要求一些額外的資訊,在我們建立你的帳戶之前。", - "interstitial.errors-found": "我們無法完成你的註冊", - "gdpr_agree_data": "I consent to the collection and processing of my personal information on this website.", - "gdpr_agree_email": "I consent to receive digest and notification emails from this website.", - "gdpr_consent_denied": "You must give consent to this site to collect/process your information, and to send you emails." + "agree_to_terms_of_use": "我同意使用條款", + "terms_of_use_error": "您必須同意使用條款", + "registration-added-to-queue": "您的註冊正在等待批准。一旦通過,管理員會發送郵件通知您。", + "interstitial.intro": "在建立您的帳戶前我們需要一些額外資訊。", + "interstitial.errors-found": "我們無法完成您的註冊:", + "gdpr_agree_data": "我同意此網站對我個人資料的收集與處理。", + "gdpr_agree_email": "我同意此網站向我發送摘要和通知郵件。", + "gdpr_consent_denied": "您需要同意此網站收集與處理您的個人資料,以及向您發送電子郵件。" } \ No newline at end of file diff --git a/public/language/zh-TW/reset_password.json b/public/language/zh-TW/reset_password.json index 8c70243b67..1f61943523 100644 --- a/public/language/zh-TW/reset_password.json +++ b/public/language/zh-TW/reset_password.json @@ -2,16 +2,16 @@ "reset_password": "重設密碼", "update_password": "更改密碼", "password_changed.title": "密碼已更改", - "password_changed.message": "

密碼重設成功,請重新登錄。", - "wrong_reset_code.title": "驗証碼不正確", - "wrong_reset_code.message": "重置驗証碼不正確。請重新輸入,或是申請新重置驗証碼。", - "new_password": "輸入新的密碼", - "repeat_password": "再次確認新密碼", - "enter_email": "請輸入你的電子郵件地址,我們會寄送郵件告訴你如何重置你的帳戶。", - "enter_email_address": "輸入電子郵件地址", - "password_reset_sent": "If the specified address corresponds to an existing user account, a password reset email was sent. Please note that only one email will be sent per minute.", - "invalid_email": "無效的電子郵件 / 電子郵件不存在!", - "password_too_short": "輸入的密碼太短,請使用另一個不同的密碼", - "passwords_do_not_match": "您輸入的兩組密碼並不吻合", - "password_expired": "你的密碼已過期,請選擇一組新密碼" + "password_changed.message": "

密碼重設成功,請重新登入。", + "wrong_reset_code.title": "重設驗證碼不正確", + "wrong_reset_code.message": "您輸入的重設驗證碼有誤,請重新輸入,或者申請新的重設驗證碼。", + "new_password": "新密碼", + "repeat_password": "驗證密碼", + "enter_email": "請輸入您的電子信箱地址,我們將會發送一份郵件協助您重設帳戶密碼。", + "enter_email_address": "輸入電子信箱地址", + "password_reset_sent": "如果指定的信箱地址關聯到已存在的帳戶,該帳戶將收到一條密碼重置郵件,請注意該郵件一分鐘內只發送一次", + "invalid_email": "無效的電子信箱/電子信箱不存在!", + "password_too_short": "密碼太短,請選擇其他密碼。", + "passwords_do_not_match": "您輸入兩個密碼不一致。", + "password_expired": "您的密碼已過期,請選擇新密碼" } \ No newline at end of file diff --git a/public/language/zh-TW/search.json b/public/language/zh-TW/search.json index ae0b67060a..1bf2f7d928 100644 --- a/public/language/zh-TW/search.json +++ b/public/language/zh-TW/search.json @@ -1,48 +1,48 @@ { - "results_matching": "有 %1 個與 \"%2\" 符合的結果 (%3秒)", - "no-matches": "沒有找到相符的主題", + "results_matching": "共 %1 條結果符合 \"%2\",(耗時 %3 秒)", + "no-matches": "無符合的結果", "advanced-search": "進階搜尋", "in": "在", "titles": "標題", - "titles-posts": "標題與張貼", - "match-words": "符合的文字", + "titles-posts": "標題和貼文", + "match-words": "關鍵字匹配", "all": "所有", "any": "任何", - "posted-by": "發表自", - "in-categories": "在類別中", - "search-child-categories": "搜尋子類別", + "posted-by": "發表", + "in-categories": "在版面", + "search-child-categories": "搜索子版面", "has-tags": "有標籤", - "reply-count": "回覆數量", - "at-least": "最少", - "at-most": "最多", - "relevance": "有關於", - "post-time": "發表時間", - "votes": "投票", - "newer-than": "較新", - "older-than": "較舊", - "any-date": "任意日期", + "reply-count": "回覆數", + "at-least": "至少", + "at-most": "至多", + "relevance": "關聯性", + "post-time": "貼文時間", + "votes": "點贊數", + "newer-than": "晚於", + "older-than": "早於", + "any-date": "任何日期", "yesterday": "昨天", - "one-week": "一周", - "two-weeks": "兩周", + "one-week": "一週", + "two-weeks": "兩週", "one-month": "一個月", "three-months": "三個月", "six-months": "六個月", "one-year": "一年", - "sort-by": "排序依照", + "sort-by": "排序", "last-reply-time": "最後回覆時間", - "topic-title": "主題", - "topic-votes": "主題投票", - "number-of-replies": "回覆數量", - "number-of-views": "閱讀數量", - "topic-start-date": "主題開始時間", - "username": "使用者名稱", - "category": "類別", + "topic-title": "主題標題", + "topic-votes": "主題點贊數", + "number-of-replies": "回文數", + "number-of-views": "查看數", + "topic-start-date": "主題開始日期", + "username": "帳戶", + "category": "版面", "descending": "降冪排序", "ascending": "升冪排序", - "save-preferences": "存到我的最愛", - "clear-preferences": "清除我的最愛", - "search-preferences-saved": "搜尋我的最愛已儲存", - "search-preferences-cleared": "搜尋我的最愛已清除", - "show-results-as": "結果顯示", - "see-more-results": "更多結果 (%1)" + "save-preferences": "存為偏好設定", + "clear-preferences": "清除偏好設定", + "search-preferences-saved": "搜尋偏好設定已儲存", + "search-preferences-cleared": "搜尋偏好設定已清除", + "show-results-as": "結果顯示為", + "see-more-results": "查看更多結果 (%1)" } \ No newline at end of file diff --git a/public/language/zh-TW/success.json b/public/language/zh-TW/success.json index daf435faad..d5b8b0089d 100644 --- a/public/language/zh-TW/success.json +++ b/public/language/zh-TW/success.json @@ -1,7 +1,7 @@ { "success": "成功", - "topic-post": "文章張貼成功", - "post-queued": "您的文章正在排隊等待審核", + "topic-post": "您已成功發佈。", + "post-queued": "您的貼文正在等待審核。", "authentication-successful": "驗證成功", - "settings-saved": "設定已儲存" + "settings-saved": "設定已儲存!" } \ No newline at end of file diff --git a/public/language/zh-TW/tags.json b/public/language/zh-TW/tags.json index 8b2046b293..654d2b5eb8 100644 --- a/public/language/zh-TW/tags.json +++ b/public/language/zh-TW/tags.json @@ -1,7 +1,7 @@ { - "no_tag_topics": "沒有此標籤的主題。", + "no_tag_topics": "此標籤還沒有主題貼文。", "tags": "標籤", - "enter_tags_here": "在這裡輸入標籤,每個介於 %1 到 %2 字元。 ", + "enter_tags_here": "在這裡輸入標籤,每個標籤 %1 到 %2 個字元。", "enter_tags_here_short": "輸入標籤...", - "no_tags": "還沒有標籤呢。" + "no_tags": "尚無標籤。" } \ No newline at end of file diff --git a/public/language/zh-TW/topic.json b/public/language/zh-TW/topic.json index cbb41fb37f..8cb35c0a52 100644 --- a/public/language/zh-TW/topic.json +++ b/public/language/zh-TW/topic.json @@ -3,137 +3,137 @@ "topic_id": "主題 ID", "topic_id_placeholder": "輸入主題 ID", "no_topics_found": "沒有找到主題!", - "no_posts_found": "找不到文章!", - "post_is_deleted": "文章已被刪除!", - "topic_is_deleted": "主題已被刪除!", + "no_posts_found": "沒有找到回覆!", + "post_is_deleted": "此回覆已被刪除!", + "topic_is_deleted": "此主題已被刪除!", "profile": "個人資料", - "posted_by": "由 %1 發表", - "posted_by_guest": "由訪客發表", + "posted_by": "%1 發佈", + "posted_by_guest": "訪客發佈", "chat": "聊天", - "notify_me": "該主題有新回覆時通知我", + "notify_me": "此主題有新回覆時通知我", "quote": "引用", "reply": "回覆", - "replies_to_this_post": "%1 回覆", - "one_reply_to_this_post": "1個回覆", + "replies_to_this_post": "%1 條回覆", + "one_reply_to_this_post": "1 條回覆", "last_reply_time": "最後回覆", - "reply-as-topic": "回復為另一個新主題", - "guest-login-reply": "登入以回覆", - "login-to-view": "🔒 Log in to view", + "reply-as-topic": "在新貼文中回覆", + "guest-login-reply": "登入後回覆", + "login-to-view": "🔒登入查看", "edit": "編輯", "delete": "刪除", "purge": "清除", - "restore": "復原", + "restore": "恢復", "move": "移動", - "change-owner": "Change Owner", - "fork": "作為主題", - "link": "鏈接", + "change-owner": "更改所有者", + "fork": "分割", + "link": "連結", "share": "分享", "tools": "工具", "locked": "已鎖定", - "pinned": "釘住", - "moved": "移動", - "copy-ip": "複製 IP", - "ban-ip": "封鎖 IP", - "view-history": "編輯紀錄", - "bookmark_instructions": "點擊這裡返回到這個討論串的最後一篇張貼文", - "flag_title": "檢舉這篇文章, 交給仲裁者來審閱.", - "merged_message": "此主題已經被合併至 %2", - "deleted_message": "此主題已被刪除。只有具有主題管理權限的使用者才能看到它。", - "following_topic.message": "有人貼文回覆主題時, 你將會收到新通知.", - "not_following_topic.message": "你將會看到這個主題在未讀主題列表中出現,但你將不會在其他人張貼到這個主題時接收到通知。", - "ignoring_topic.message": "你將不會再未讀主題列表中看到這個主題。當你被提及或你的張貼被正向投票時,你會被通知。", - "login_to_subscribe": "請先註冊或登錄, 才可訂閱此主題.", - "markAsUnreadForAll.success": "將全部的主題設為未讀.", - "mark_unread": "標為未讀", - "mark_unread.success": "標記主題為未讀", + "pinned": "已置頂", + "moved": "已移動", + "copy-ip": "複製IP", + "ban-ip": "禁用IP", + "view-history": "編輯歷史", + "bookmark_instructions": "點擊閱讀本主題貼文中的最新回覆", + "flag_title": "舉報此貼文", + "merged_message": "此主題已併入%2", + "deleted_message": "此主題已被刪除。只有擁有主題管理權限的使用者可以查看。", + "following_topic.message": "當有人回覆此主題時,您會收到通知。", + "not_following_topic.message": "您將在未讀主題列表中看到這個主題,但您不會在貼文被回覆時收到通知。", + "ignoring_topic.message": "您將不會在未讀主題列表裡看到這個主題,但在被提及以及貼文被點贊時仍將收到通知。", + "login_to_subscribe": "請註冊或登入後,再訂閱此主題。", + "markAsUnreadForAll.success": "將全部主題標為未讀。", + "mark_unread": "標記為未讀", + "mark_unread.success": "主題已被標記為未讀。", "watch": "關注", "unwatch": "取消關注", - "watch.title": "當主題有新回覆時將收到通知", - "unwatch.title": "停止關注這個主題", - "share_this_post": "分享這篇文章", - "watching": "關注", - "not-watching": "取消關注", - "ignoring": "忽略", - "watching.description": "有新的回覆通知我。
在未讀頁中顯示主題。", - "not-watching.description": "有新的回覆不用通知我。
如果類別不是被忽略的,在未讀頁中顯示主題。", - "ignoring.description": "有新的回覆不用通知我。
在未讀頁中不顯示主題。", - "thread_tools.title": "主題工具箱", - "thread_tools.markAsUnreadForAll": "將所有標記為未讀", - "thread_tools.pin": "釘選主題", - "thread_tools.unpin": "取消釘選主題", + "watch.title": "當此主題有新回覆時,通知我", + "unwatch.title": "取消關注此主題", + "share_this_post": "分享此貼文", + "watching": "關注中", + "not-watching": "未關注", + "ignoring": "忽略中", + "watching.description": "有新回覆時通知我。
在未讀主題中顯示。", + "not-watching.description": "不要在有新回覆時通知我。
如果這個版面未被忽略則在未讀主題中顯示。", + "ignoring.description": "不要在有新回覆時通知我。
不要在未讀主題中顯示該主題。", + "thread_tools.title": "主題工具", + "thread_tools.markAsUnreadForAll": "全部標記為未讀", + "thread_tools.pin": "置頂主題", + "thread_tools.unpin": "取消置頂主題", "thread_tools.lock": "鎖定主題", - "thread_tools.unlock": "解除主題鎖定", + "thread_tools.unlock": "解鎖主題", "thread_tools.move": "移動主題", - "thread_tools.move-posts": "移動文章", + "thread_tools.move-posts": "移動貼文", "thread_tools.move_all": "移動全部", - "thread_tools.change_owner": "Change Owner", - "thread_tools.select_category": "選擇類別", - "thread_tools.fork": "分叉主題", + "thread_tools.change_owner": "更改所有者", + "thread_tools.select_category": "選擇版面", + "thread_tools.fork": "分割主題", "thread_tools.delete": "刪除主題", - "thread_tools.delete-posts": "刪除文章", - "thread_tools.delete_confirm": "你確定要刪除這個主題?", - "thread_tools.restore": "還原刪除的主題", - "thread_tools.restore_confirm": "你確定你要恢復這個主題嗎?", + "thread_tools.delete-posts": "刪除回覆", + "thread_tools.delete_confirm": "確定要刪除此主題嗎?", + "thread_tools.restore": "恢復主題", + "thread_tools.restore_confirm": "確定要恢復此主題嗎?", "thread_tools.purge": "清除主題", - "thread_tools.purge_confirm": "你確定要清除這個主題?", + "thread_tools.purge_confirm": "確認清除此主題嗎?", "thread_tools.merge_topics": "合併主題", "thread_tools.merge": "合併", - "topic_move_success": "主題已成功移至 %1", - "post_delete_confirm": "你確定要刪除這文章嗎?", - "post_restore_confirm": "你確定要還原這文章嗎?", - "post_purge_confirm": "你確定要清除這個文章嗎?", - "load_categories": "版面載入中", + "topic_move_success": "此主題已成功移到 %1", + "post_delete_confirm": "您確定要刪除此回覆嗎?", + "post_restore_confirm": "您確定要恢復此回覆嗎?", + "post_purge_confirm": "您確定要清除此回覆嗎?", + "load_categories": "正在載入版面", "confirm_move": "移動", - "confirm_fork": "作為主題", + "confirm_fork": "分割", "bookmark": "書籤", "bookmarks": "書籤", - "bookmarks.has_no_bookmarks": "你尚未將任何文章加入書籤", - "loading_more_posts": "載入更多文章", + "bookmarks.has_no_bookmarks": "您還沒有加入任何書籤", + "loading_more_posts": "正在載入更多貼文", "move_topic": "移動主題", "move_topics": "移動主題", - "move_post": "移動文章", - "post_moved": "文章已移動!", - "fork_topic": "作為主題", - "fork_topic_instruction": "點擊將分割的文章", - "fork_no_pids": "尚未選擇文章!", - "fork_pid_count": "已選擇 %1 篇文章", - "fork_success": "成功分叉成新的主題!點擊這裡進入新的主題。", - "delete_posts_instruction": "點擊你想要刪除/清除的文章", - "merge_topics_instruction": "選擇你想要合併的主題", - "move_posts_instruction": "點擊你想移動的文章", - "change_owner_instruction": "Click the posts you want to assign to another user", - "composer.title_placeholder": "輸入標題...", - "composer.handle_placeholder": "名字", - "composer.discard": "放棄", - "composer.submit": "發表", - "composer.replying_to": "回覆給 %1", + "move_post": "移動貼文", + "post_moved": "回覆已移動!", + "fork_topic": "分割主題", + "fork_topic_instruction": "點擊將分割的貼文", + "fork_no_pids": "未選中貼文!", + "fork_pid_count": "選擇了 %1 個貼文", + "fork_success": "成功分割主題! 點這裡跳轉到分割後的主題。", + "delete_posts_instruction": "點擊想要刪除/永久刪除的貼文", + "merge_topics_instruction": "點擊您想合併的主題", + "move_posts_instruction": "點擊您想要移動的貼文", + "change_owner_instruction": "點擊您想轉移給其他使用者的貼文", + "composer.title_placeholder": "在此輸入您主題的標題...", + "composer.handle_placeholder": "Enter your name/handle here", + "composer.discard": "撤銷", + "composer.submit": "提交", + "composer.replying_to": "正在回覆 %1", "composer.new_topic": "新主題", - "composer.uploading": "上傳中...", - "composer.thumb_url_label": "粘貼一個主題縮略圖網址", - "composer.thumb_title": "添加縮略圖到這個主題", + "composer.uploading": "正在上傳...", + "composer.thumb_url_label": "添加主題縮圖網址", + "composer.thumb_title": "給此主題添加縮圖", "composer.thumb_url_placeholder": "http://example.com/thumb.png", "composer.thumb_file_label": "或上傳檔案", - "composer.thumb_remove": "清除所有欄目", - "composer.drag_and_drop_images": "拖曳影像到此", - "more_users_and_guests": "%1 個使用者和 %2個訪客", - "more_users": "%1 個使用者", - "more_guests": "%1 個訪客", - "users_and_others": "%1 和另外 %2 個人", - "sort_by": "排序方式", + "composer.thumb_remove": "清除欄位", + "composer.drag_and_drop_images": "拖曳圖片到此處", + "more_users_and_guests": "%1 名使用者和 %2 名訪客", + "more_users": "%1 名使用者", + "more_guests": "%1 名訪客", + "users_and_others": "%1 和 %2 其他人", + "sort_by": "排序", "oldest_to_newest": "從舊到新", "newest_to_oldest": "從新到舊", - "most_votes": "最高票數", - "most_posts": "發表最多", - "stale.title": "改為建立新的主題?", - "stale.warning": "你正回覆的主題是非常舊的一篇。你想要改為建立一個新主題,然後參考到這篇你回覆的?", + "most_votes": "最多點贊", + "most_posts": "回覆最多", + "stale.title": "接受建議並建立新主題?", + "stale.warning": "您回覆的主題已經很古老了,是否發佈新主題並引用此主題的內容?", "stale.create": "建立新主題", - "stale.reply_anyway": "無論如何都回覆這個主題", + "stale.reply_anyway": "仍然回覆此貼文", "link_back": "回覆: [%1](%2)", - "diffs.title": "文章編輯紀錄", - "diffs.description": "此文章有 %1 個版本。點擊下列其中一個版本來查看文章內容。", - "diffs.no-revisions-description": "此文章有 %1 個版本.", - "diffs.current-revision": "目前版本", + "diffs.title": "歷史發佈記錄", + "diffs.description": "此主題已經重新發布並修訂。點擊某個時間點查看修訂的內容。", + "diffs.no-revisions-description": "該貼文已重新修改", + "diffs.current-revision": "當前版本", "diffs.original-revision": "原始版本", - "timeago_later": "%1 later", - "timeago_earlier": "%1 earlier" + "timeago_later": "%1 後", + "timeago_earlier": "%1 前" } \ No newline at end of file diff --git a/public/language/zh-TW/unread.json b/public/language/zh-TW/unread.json index 8bcf2b103a..f20b451ede 100644 --- a/public/language/zh-TW/unread.json +++ b/public/language/zh-TW/unread.json @@ -2,14 +2,14 @@ "title": "未讀", "no_unread_topics": "沒有未讀主題。", "load_more": "載入更多", - "mark_as_read": "標記成已讀", - "selected": "已選擇", + "mark_as_read": "標為已讀", + "selected": "已選", "all": "全部", - "all_categories": "所有類別", - "topics_marked_as_read.success": "主題皆標記為已讀!", - "all-topics": "所有主題", - "new-topics": "新主題", - "watched-topics": "已觀看主題", - "unreplied-topics": "為回覆的主題", + "all_categories": "全部版面", + "topics_marked_as_read.success": "主題被標為已讀!", + "all-topics": "全部主題", + "new-topics": "新建主題", + "watched-topics": "關注主題", + "unreplied-topics": "未回覆主題", "multiple-categories-selected": "多選" } \ No newline at end of file diff --git a/public/language/zh-TW/uploads.json b/public/language/zh-TW/uploads.json index 54a3b977ec..a0b1451682 100644 --- a/public/language/zh-TW/uploads.json +++ b/public/language/zh-TW/uploads.json @@ -1,9 +1,9 @@ { - "uploading-file": "檔案上傳中...", - "select-file-to-upload": "選擇要上傳的檔案!", - "upload-success": "檔案已成功上傳!", - "maximum-file-size": "最大尺寸 %1 kb", - "no-uploads-found": "找不到上傳檔案", - "public-uploads-info": "上傳是公開的,所有訪客皆可以看到。", - "private-uploads-info": "上傳是私有的,只有登入的使用者才可以看到。" + "uploading-file": "正在上傳檔案...", + "select-file-to-upload": "請選擇需要上傳的檔案!", + "upload-success": "檔案上傳成功!", + "maximum-file-size": "最大 %1 kb", + "no-uploads-found": "沒有找到上傳檔案", + "public-uploads-info": "上傳公開的檔案,所有訪客均可查看。", + "private-uploads-info": "上傳私有的檔案,僅登入使用者可見。" } \ No newline at end of file diff --git a/public/language/zh-TW/user.json b/public/language/zh-TW/user.json index f3cb7f54a1..58eba30922 100644 --- a/public/language/zh-TW/user.json +++ b/public/language/zh-TW/user.json @@ -1,176 +1,176 @@ { - "banned": "已封鎖", - "offline": "下線", + "banned": "已停權", + "offline": "離線", "deleted": "已刪除", - "username": "使用者名稱", - "joindate": "加入時間", - "postcount": "文章數量", - "email": "電子郵件", - "confirm_email": "確認電子郵件", - "account_info": "帳戶資訊", - "ban_account": "禁用帳號", - "ban_account_confirm": "你確定要禁用這個使用者?", - "unban_account": "取消禁用帳號", + "username": "使用者名", + "joindate": "註冊日期", + "postcount": "貼文數量", + "email": "電子信箱", + "confirm_email": "確認電子信箱", + "account_info": "帳戶訊息", + "ban_account": "禁用帳戶", + "ban_account_confirm": "您確定要禁用此帳戶、嗎?", + "unban_account": "解禁帳戶", "delete_account": "刪除帳戶", - "delete_account_confirm": "您確定要刪除您自己的帳戶?
此項操作是不能還原的,而且您將無法回復任何您的所有資料。

輸入您的密碼用以確認您決定要刪除這個帳戶。", - "delete_this_account_confirm": "您確定要刪除這個帳戶?
此操作是不能還原的,您將無法回復任何資料
", - "account-deleted": "帳號已刪除", - "fullname": "全名", + "delete_account_confirm": "您確定要刪除您的帳戶嗎?
本操作不可逆, 您的資料都會被移除且不可恢復。

輸入密碼以確認您確實需要刪除您的帳戶。", + "delete_this_account_confirm": "您確定要刪除帳戶嗎?
這個操作不可逆,而且您可能不能找回任何資料

", + "account-deleted": "帳戶已刪除", + "fullname": "姓名", "website": "網站", - "location": "地址", + "location": "位置", "age": "年齡", - "joined": "加入時間", - "lastonline": "最後上線", + "joined": "註冊時間", + "lastonline": "最後登入", "profile": "個人資料", - "profile_views": "個人資料觀看次數", - "reputation": "聲譽", + "profile_views": "個人資料瀏覽", + "reputation": "聲望", "bookmarks": "書籤", - "watched_categories": "已檢視目錄", - "change_all": "Change All", - "watched": "觀看者", - "ignored": "已略過", - "default-category-watch-state": "預設觀看類別", - "followers": "跟隨者", - "following": "正在關注", - "blocks": "區塊", - "block_toggle": "切換區塊", - "block_user": "封鎖用戶", - "unblock_user": "解除封鎖用戶", + "watched_categories": "已關注的版面", + "change_all": "全部更改", + "watched": "已關注", + "ignored": "已忽略", + "default-category-watch-state": "預設版面關注狀態", + "followers": "追隨者", + "following": "追隨", + "blocks": "屏蔽", + "block_toggle": "封鎖該使用者", + "block_user": "封鎖使用者", + "unblock_user": "解封使用者", "aboutme": "關於我", - "signature": "簽名", + "signature": "簽名檔", "birthday": "生日", "chat": "聊天", "chat_with": "繼續與 %1 聊天", - "new_chat_with": "和 %1 開始新的對話", - "flag-profile": "標記個人資料", - "follow": "追蹤", - "unfollow": "取消追蹤", + "new_chat_with": "開始與 %1 的新對話", + "flag-profile": "舉報個人檔案", + "follow": "追隨", + "unfollow": "取消追隨", "more": "更多", - "profile_update_success": "你的個人資料已更新成功!", - "change_picture": "改變大頭貼", - "change_username": "更改帳號", - "change_email": "更改電子郵件", - "email_same_as_password": "請輸入您現在的密碼來繼續 – 您已經再次登入您的新電子郵件。", + "profile_update_success": "資料已經成功更新。", + "change_picture": "更改頭像", + "change_username": "更改帳戶", + "change_email": "更改電子信箱", + "email_same_as_password": "請輸入您當前的密碼以繼續 –您已經再次輸入了您的新電子信箱", "edit": "編輯", - "edit-profile": "編輯個人資料", - "default_picture": "預設圖示", - "uploaded_picture": "已更新的大頭貼", - "upload_new_picture": "上傳新大頭貼", - "upload_new_picture_from_url": "從網址上傳新大頭貼", - "current_password": "目前的密碼", + "edit-profile": "編輯個人檔案", + "default_picture": "預設頭像", + "uploaded_picture": "已有頭像", + "upload_new_picture": "上傳新頭像", + "upload_new_picture_from_url": "上傳來自URL的新頭像", + "current_password": "當前密碼", "change_password": "更改密碼", "change_password_error": "無效的密碼!", - "change_password_error_wrong_current": "目前的密碼不正確!", - "change_password_error_match": "密碼必須要一致!", - "change_password_error_privileges": "你沒有變更此密碼的權限!", - "change_password_success": "你的密碼已經更新!", + "change_password_error_wrong_current": "您的當前密碼不正確!", + "change_password_error_match": "兩次輸入的密碼必須相同!", + "change_password_error_privileges": "您無權更改此密碼。", + "change_password_success": "您的密碼已更新!", "confirm_password": "確認密碼", "password": "密碼", - "username_taken_workaround": "你想要註冊的帳號已經被使用了,所以我們將它略作改變。你現在的帳號名稱是 %1", - "password_same_as_username": "你的密碼和帳號是一樣的,請選擇另一組密碼。", - "password_same_as_email": "你的密碼和電子郵件是一樣的,請選擇另一組密碼。", - "weak_password": "密碼強度太弱。", - "upload_picture": "上傳照片", - "upload_a_picture": "上傳一張照片", - "remove_uploaded_picture": "移除上傳的照片", - "upload_cover_picture": "上傳封面照片", - "remove_cover_picture_confirm": "你確定要移除封面照片嗎?", - "crop_picture": "裁切照片", - "upload_cropped_picture": "裁切並上傳", + "username_taken_workaround": "您申請的帳戶已被佔用,所以我們稍作更改。您現在的帳戶是 %1", + "password_same_as_username": "您的密碼與帳戶相同,請選擇另外的密碼。", + "password_same_as_email": "您的密碼與郵箱相同,請選擇另外的密碼。", + "weak_password": "密碼強度低。", + "upload_picture": "上傳頭像", + "upload_a_picture": "上傳頭像", + "remove_uploaded_picture": "刪除已上傳的頭像", + "upload_cover_picture": "上傳封面圖片", + "remove_cover_picture_confirm": "您確定要移除封面圖片嗎?", + "crop_picture": "剪裁圖片", + "upload_cropped_picture": "剪裁併上傳", "settings": "設定", - "show_email": "顯示我的電子郵件地址", + "show_email": "顯示我的電子信箱", "show_fullname": "顯示我的全名", - "restrict_chats": "只允許我跟隨的使用者和我聊天", + "restrict_chats": "只允許我追隨的使用者給我發送聊天訊息", "digest_label": "訂閱摘要", - "digest_description": "根據你所設的時間排程,用電子郵件訂閱這個討論區 (新的通知與主題)。", + "digest_description": "訂閱此論壇的定期電子郵件更新 (新通知和主題)", "digest_off": "關閉", - "digest_daily": "每日", + "digest_daily": "每天", "digest_weekly": "每週", "digest_monthly": "每月", - "has_no_follower": "該使用者還沒有被任何人關注。", - "follows_no_one": "該使用者還沒有關注過任何人。", - "has_no_posts": "使用者還沒有發表任何張貼", - "has_no_topics": "使用者還沒有發表任何主題", - "has_no_watched_topics": "使用者還沒有觀看任何主題", - "has_no_ignored_topics": "使用者還沒有忽略任何主題", - "has_no_upvoted_posts": "使用者還沒有對任何主題投正向票", - "has_no_downvoted_posts": "使用者還沒有對任何主題投負向票", - "has_no_voted_posts": "這個使用者沒有投票的張貼", - "has_no_blocks": "您並沒有封鎖任何用戶。", - "email_hidden": "電子郵件地址被隱藏", + "has_no_follower": "此使用者還沒有追隨者 :(", + "follows_no_one": "此使用者尚未追隨任何人 :(", + "has_no_posts": "此使用者從未發文。", + "has_no_topics": "此使用者還未發佈任何主題。", + "has_no_watched_topics": "此使用者還未關注任何主題。", + "has_no_ignored_topics": "此使用者尚未忽略任何主題。", + "has_no_upvoted_posts": "此使用者還未點贊過任何貼文。", + "has_no_downvoted_posts": "此使用者還未倒讚過任何貼文。", + "has_no_voted_posts": "這個使用者還未評價任何貼文", + "has_no_blocks": "您沒有封鎖其他使用者。", + "email_hidden": "電子信箱已隱藏", "hidden": "隱藏", - "paginate_description": "將主題與張貼用分頁來顯示,取代使用無盡的捲動方式。", - "topics_per_page": "每頁的主題數", - "posts_per_page": "每頁的文章數", + "paginate_description": "使用分頁式版面瀏覽", + "topics_per_page": "每頁主題數", + "posts_per_page": "每頁貼文數", "max_items_per_page": "最大值 %1", - "acp_language": "管理頁面語言", - "notification_sounds": "當你接收到通知時撥放音效", - "notifications_and_sounds": "通知&音效", - "incoming-message-sound": "收到訊息音效", - "outgoing-message-sound": "發出訊息音效", - "notification-sound": "通知音效", - "no-sound": "沒有聲音", - "upvote-notif-freq": "按讚通知頻率", - "upvote-notif-freq.all": "所有按讚", - "upvote-notif-freq.first": "美篇文章的第一則", - "upvote-notif-freq.everyTen": "每十則按讚", - "upvote-notif-freq.threshold": "於 1, 5, 10, 25, 50, 100, 150, 200...", - "upvote-notif-freq.logarithmic": "於 10, 100, 1000...", - "upvote-notif-freq.disabled": "停用", + "acp_language": "控制台頁面語言", + "notification_sounds": "收到通知時播放提示音", + "notifications_and_sounds": "通知 & 提示音", + "incoming-message-sound": "訊息到達提示音", + "outgoing-message-sound": "訊息送出提示音", + "notification-sound": "通知提示音", + "no-sound": "無提示音", + "upvote-notif-freq": "貼文被讚的通知頻率", + "upvote-notif-freq.all": "每一次被讚都通知我", + "upvote-notif-freq.first": "首次點贊貼文", + "upvote-notif-freq.everyTen": "每10次被點贊通知我一次", + "upvote-notif-freq.threshold": "當被點贊的數目為1, 5, 10, 25, 50, 100, 150, 200...時通知我", + "upvote-notif-freq.logarithmic": "當被點讚的數目為10, 100, 1000...時通知我", + "upvote-notif-freq.disabled": "任何時候都不要通知我", "browsing": "瀏覽設定", - "open_links_in_new_tab": "在新的資料標籤裡打開外部的連結", - "enable_topic_searching": "啟用在主題中的搜尋", - "topic_search_help": "如果啟用的話,在主題中搜尋將會覆蓋瀏覽器預設網頁搜尋的行為,這會允許你搜尋整篇的主題,而不只是顯示出來在畫面上的而已。", - "scroll_to_my_post": "在張貼回覆後,顯示新的張貼", - "follow_topics_you_reply_to": "關注你有回覆的主題", - "follow_topics_you_create": "關注你建立的主題", - "grouptitle": "群組標題", - "group-order-help": "Select a group and use the arrows to order titles", - "no-group-title": "無此群組標題", - "select-skin": "選擇外觀", + "open_links_in_new_tab": "在新頁籤打開外部連結", + "enable_topic_searching": "啟用主題內搜索", + "topic_search_help": "如果啟用此項,主題內搜索會替代瀏覽器預設的頁面搜索,您將可以在整個主題內搜索,而不僅僅只搜索頁面上展現的內容。", + "scroll_to_my_post": "在提交回覆之後顯示新回覆", + "follow_topics_you_reply_to": "關注您回覆過的主題", + "follow_topics_you_create": "關注您建立的主題", + "grouptitle": "群組稱號", + "group-order-help": "選擇群組然後使用箭頭排列稱號", + "no-group-title": "不顯示群組稱號", + "select-skin": "選擇配色", "select-homepage": "選擇首頁", "homepage": "首頁", - "homepage_description": "選擇一個要用來當作討論區的首頁的頁面,或是選None使用預設的首頁。", - "custom_route": "自訂首頁路由", - "custom_route_help": "在這裡輸入路由名稱,不需要在前面的斜線(例如recent或popular)", - "sso.title": "單一簽入SSO服務", - "sso.associated": "關連於", - "sso.not-associated": "點擊這裡進行關連於", - "sso.dissociate": "關聯", - "sso.dissociate-confirm-title": "確認斷開關聯", - "sso.dissociate-confirm": "你確定要將你的帳戶斷開與 %1 關聯嘛?", - "info.latest-flags": "最近標註", - "info.no-flags": "沒有找到標註的張貼", - "info.ban-history": "最近禁用歷史", - "info.no-ban-history": "這個使用者永遠不會被禁用", - "info.banned-until": "禁用至 %1", + "homepage_description": "選擇一個頁面作為論壇的首頁,否則設置為 ‘空’ 使用預設首頁。", + "custom_route": "自訂首頁路徑", + "custom_route_help": "輸入路由名稱,前面不需要斜線 ( 例如, \"recent\" 或 \"popular\" )", + "sso.title": "單一簽入服務", + "sso.associated": "已關聯到", + "sso.not-associated": "點擊這裡來關聯", + "sso.dissociate": "解除關聯", + "sso.dissociate-confirm-title": "確認解除關聯", + "sso.dissociate-confirm": "您確定要將您的帳戶與 %1 解除關聯嗎?", + "info.latest-flags": "最新舉報", + "info.no-flags": "沒有找到被舉報的貼文", + "info.ban-history": "最近停權紀錄", + "info.no-ban-history": "該使用者從未被停權", + "info.banned-until": "停權直到 %1", "info.banned-permanently": "永久禁用", - "info.banned-reason-label": "理由", - "info.banned-no-reason": "沒有給理由", - "info.username-history": "帳戶名稱紀錄", - "info.email-history": "Email紀錄", - "info.moderation-note": "Moderation紀錄", - "info.moderation-note.success": "儲存Moderation紀錄", - "info.moderation-note.add": "增加筆記", - "sessions.description": "This page allows you to view any active sessions on this forum and revoke them if necessary. You can revoke your own session by logging out of your account.", - "consent.title": "Your Rights & Consent", - "consent.lead": "This community forum collects and processes your personal information.", - "consent.intro": "We use this information strictly to personalise your experience in this community, as well as to associate the posts you make to your user account. During the registration step you were asked to provide a username and email address, you can also optionally provide additional information to complete your user profile on this website.

We retain this information for the life of your user account, and you are able to withdraw consent at any time by deleting your account. At any time you may request a copy of your contribution to this website, via your Rights & Consent page.

If you have any questions or concerns, we encourage you to reach out to this forum's administrative team.", - "consent.email_intro": "Occasionally, we may send emails to your registered email address in order to provide updates and/or to notify you of new activity that is pertinent to you. You can customise the frequency of the community digest (including disabling it outright), as well as select which types of notifications to receive via email, via your user settings page.", - "consent.digest_frequency": "Unless explicitly changed in your user settings, this community delivers email digests every %1.", - "consent.digest_off": "Unless explicitly changed in your user settings, this community does not send out email digests", - "consent.received": "You have provided consent for this website to collect and process your information. No additional action is required.", - "consent.not_received": "You have not provided consent for data collection and processing. At any time this website's administration may elect to delete your account in order to become compliant with the General Data Protection Regulation.", - "consent.give": "Give consent", - "consent.right_of_access": "You have the Right of Access", - "consent.right_of_access_description": "You have the right to access any data collected by this website upon request. You can retrieve a copy of this data by clicking the appropriate button below.", - "consent.right_to_rectification": "You have the Right to Rectification", - "consent.right_to_rectification_description": "You have the right to change or update any inaccurate data provided to us. Your profile can be updated by editing your profile, and post content can always be edited. If this is not the case, please contact this site's administrative team.", - "consent.right_to_erasure": "You have the Right to Erasure", - "consent.right_to_erasure_description": "At any time, you are able to revoke your consent to data collection and/or processing by deleting your account. Your individual profile can be deleted, although your posted content will remain. If you wish to delete both your account and your content, please contact the administrative team for this website.", - "consent.right_to_data_portability": "You have the Right to Data Portability", - "consent.right_to_data_portability_description": "You may request from us a machine-readable export of any collected data about you and your account. You can do so by clicking the appropriate button below.", - "consent.export_profile": "匯出個人資料 (.csv)", - "consent.export_uploads": "匯出上傳的內容 (.zip)", - "consent.export_posts": "匯出文章 (.csv)" + "info.banned-reason-label": "原因", + "info.banned-no-reason": "沒有原因", + "info.username-history": "用過的使用者名", + "info.email-history": "用過的電子信箱", + "info.moderation-note": "版主備註", + "info.moderation-note.success": "版主備註已儲存", + "info.moderation-note.add": "新增備註", + "sessions.description": "此頁面允許您查看當前論壇的所有當前的會話(active session),並在需要的時候關閉它們.您可以通過登出您的帳戶來關閉自己的會話(session)", + "consent.title": "您的權利與許可", + "consent.lead": "本論壇將會收集與處理您的個人資料。", + "consent.intro": "我們收集這些資料將僅用於個人化您於本社區的體驗,和關聯您的帳戶與您所發表的貼文。在註冊過程中您需要提供一個使用者名和信箱地址,您也可以選擇是否提供額外的個人資料,以完善您的使用者檔案。

在您的帳戶有效期內,我們將保留您的資料。您可以在任何時候通過刪除您的帳戶,以撤回您的許可。您可以在任何時候通過您的權力與許可頁面,獲取一份您對本論壇的貢獻的副本。

如果您有任何疑問,我們鼓勵您與本論壇管理團隊聯繫。", + "consent.email_intro": "我們有時可能會向您的註冊信箱發送電子郵件,以向您提供有關於您的新動態和/或新活動。您可以通過您的使用者設定頁面自訂(包括直接禁用)社區摘要的發送頻率,以及選擇性地接收哪些類型的通知。", + "consent.digest_frequency": "本社區預設每 %1 發送一封摘要郵件,除非您在使用者設定中明確更改了此設定。", + "consent.digest_off": "本社區預設不發送摘要郵件,除非您在使用者設置中明確更改了此設定。", + "consent.received": "您已許可本網站收集與處理您的個人資料。無需其它額外操作。", + "consent.not_received": "您未許可本網站收集與處理您的個人資料。本網站的管理團隊可能於任何時候刪除您的帳戶,以符合通用資料保護條例的要求。", + "consent.give": "授予許可", + "consent.right_of_access": "您擁有資料訪問權", + "consent.right_of_access_description": "您有權訪問本網站根據需求收集的您的任何資料。您可以點擊下方相應按鈕,獲取這些數據的副本。", + "consent.right_to_rectification": "您擁有資料更正權", + "consent.right_to_rectification_description": "您擁有修改或更新提供給我們的任何不準確的個人資料的權力。您可以通過編輯以更新個人資料,並可以修改您發表的內容。若非如此,請聯繫本網站的管理團隊。", + "consent.right_to_erasure": "您擁有被遺忘權", + "consent.right_to_erasure_description": "您隨時都可以通過刪除帳戶,來撤銷資料收集和處理的許可。您的個人檔案可以被刪除,但是您發表的內容仍然會保留。如果您想要同時刪除您的帳戶內容,請聯繫此網站的管理團隊。", + "consent.right_to_data_portability": "您擁有資料轉移權", + "consent.right_to_data_portability_description": "您也許想導出有關您和您的帳戶的機器可讀副本。您可以點擊下方的按鈕來獲取它們。", + "consent.export_profile": "導出個人資料 (.csv)", + "consent.export_uploads": "導出上傳檔案 (.zip)", + "consent.export_posts": "導出貼文 (.csv)" } \ No newline at end of file diff --git a/public/language/zh-TW/users.json b/public/language/zh-TW/users.json index 0b7afe087c..b150cb7d42 100644 --- a/public/language/zh-TW/users.json +++ b/public/language/zh-TW/users.json @@ -1,22 +1,22 @@ { - "latest_users": "最近使用者", - "top_posters": "發文數最多", - "most_reputation": "聲望最高", - "most_flags": "最多標註", + "latest_users": "最新使用者", + "top_posters": "發文排行", + "most_reputation": "聲望排行", + "most_flags": "舉報最多", "search": "搜尋", - "enter_username": "輸入想找的使用者帳號", + "enter_username": "輸入使用者名搜索", "load_more": "載入更多", - "users-found-search-took": "找到 %1 使用者! 搜尋用了 %2 秒。", - "filter-by": "篩選依照", - "online-only": "線上僅有", - "invite": "邀請", - "prompt-email": "Email", - "invitation-email-sent": "所有邀請函皆已被寄到 %1", + "users-found-search-took": "找到 %1 位使用者!耗時 %2 秒。", + "filter-by": "過濾選項", + "online-only": "只看在線", + "invite": "邀請註冊", + "prompt-email": "郵件:", + "invitation-email-sent": "已發送邀請給 %1", "user_list": "使用者列表", - "recent_topics": "最新的主題", - "popular_topics": "熱門的主題", - "unread_topics": "未讀的主題", - "categories": "類別", + "recent_topics": "最新主題", + "popular_topics": "熱門主題", + "unread_topics": "未讀主題", + "categories": "版面", "tags": "標籤", - "no-users-found": "沒有找到使用者!" + "no-users-found": "未找到符合的使用者!" } \ No newline at end of file diff --git a/public/openapi/components/schemas/Breadcrumbs.yaml b/public/openapi/components/schemas/Breadcrumbs.yaml new file mode 100644 index 0000000000..986e3544cb --- /dev/null +++ b/public/openapi/components/schemas/Breadcrumbs.yaml @@ -0,0 +1,16 @@ +Breadcrumbs: + type: object + properties: + breadcrumbs: + type: array + items: + type: object + properties: + text: + type: string + url: + type: string + cid: + type: number + required: + - text \ No newline at end of file diff --git a/public/openapi/components/schemas/CategoryObject.yaml b/public/openapi/components/schemas/CategoryObject.yaml new file mode 100644 index 0000000000..fa8af2d41d --- /dev/null +++ b/public/openapi/components/schemas/CategoryObject.yaml @@ -0,0 +1,65 @@ +CategoryObject: + type: object + properties: + cid: + type: number + description: A category identifier assigned upon category creation (this value cannot be changed) + name: + type: string + description: The category's name/title + description: + type: string + description: A variable-length description of the category (usually displayed underneath the category name) + descriptionParsed: + type: string + description: A variable-length description of the category (usually displayed underneath the category name). Unlike `description`, this value here will have been run through any parsers installed on the forum (e.g. Markdown) + icon: + type: string + description: A FontAwesome icon string + example: fa-comments-o + bgColor: + type: string + description: Theme-related, a six-character hexadecimal string representing the background colour of the category + color: + type: string + description: Theme-related, a six-character hexadecimal string representing the foreground/text colour of the category + slug: + type: string + description: An URL-safe variant of the category title. This value is automatically generated. + readOnly: true + parentCid: + type: number + description: The category identifier for the category that is the immediate ancestor of the current category + topic_count: + type: number + description: The number of topics in the category + post_count: + type: number + description: The number of posts in the category + disabled: + type: number + description: Whether or not this category is disabled. + order: + type: number + description: A number representing the category's place in the hierarchy + link: + type: string + description: If set, attempting to access the forum will go to this external link instead (theme-specific) + numRecentReplies: + type: number + description: The number of posts to render in the API response (this is mostly used at the theme level) + class: + type: string + description: Values that are appended to the `class` attribute of the category's parent/root element + imageClass: + type: string + enum: [auto, cover, contain] + description: The `background-position` of the category background image, if one is set + isSection: + type: number + totalPostCount: + type: number + description: The number of posts in the category + totalTopicCount: + type: number + description: The number of topics in the category \ No newline at end of file diff --git a/public/openapi/components/schemas/CommonProps.yaml b/public/openapi/components/schemas/CommonProps.yaml new file mode 100644 index 0000000000..1ba5a30599 --- /dev/null +++ b/public/openapi/components/schemas/CommonProps.yaml @@ -0,0 +1,79 @@ +CommonProps: + type: object + properties: + loggedIn: + type: boolean + description: True if user is logged in, false otherwise + relative_path: + type: string + description: | + If NodeBB is installed in a subfolder this becomes the path to the forum. For example if your forum url is + `example.org/community` then relative_path will be `/community`. If your forum url is `example.com` then relative path will be an empty string. + template: + type: object + properties: + name: + type: string + description: The path to the template, which acts as a unique name + example: admin/settings/general + additionalProperties: + description: There will be one additional property added to all routes here. It is a boolean value whose key is the path to the current template. It is used on the client-side to verify the current page inside of a conditional (e.g. `if (ajaxify.data.template.topic)` to ensure a script is run only on the topic page) + type: boolean + enum: [true] + url: + type: string + description: Base url of the current page, does not include query params + bodyClass: + type: string + description: The css class string that is appended to the body element + _header: + type: object + description: List of meta and link tags that are added to the head element + properties: + tags: + type: object + properties: + meta: + type: array + items: + type: object + properties: + name: + type: string + content: + type: string + noEscape: + type: boolean + property: + type: string + required: + - content + link: + type: array + items: + type: object + properties: + rel: + type: string + type: + type: string + href: + type: string + title: + type: string + sizes: + type: string + required: + - rel + - href + widgets: + type: object + description: Each widget area will have its own property in this object + additionalProperties: + type: array + description: A collection of HTML snippets that are appended to each widget area + items: + type: object + properties: + html: + type: string \ No newline at end of file diff --git a/public/openapi/components/schemas/GroupObject.yaml b/public/openapi/components/schemas/GroupObject.yaml new file mode 100644 index 0000000000..89b4cf9a4a --- /dev/null +++ b/public/openapi/components/schemas/GroupObject.yaml @@ -0,0 +1,133 @@ +GroupFullObject: + type: object + description: The response from an internal call to `Groups.get()` + properties: + name: + type: string + description: The group name + slug: + type: string + description: URL-safe slug of the group name + createtime: + type: number + description: UNIX timestamp of the group's creation + userTitle: + type: number + description: Label text for the user badge + userTitleEnabled: + type: number + description: + type: string + description: The group description + memberCount: + type: number + hidden: + type: number + system: + type: number + private: + type: number + disableJoinRequests: + type: number + disableLeave: + type: number + nameEncoded: + type: string + displayName: + type: string + description: A custom override of the group's name, a friendly name + labelColor: + type: string + description: A six-character hexadecimal colour code + textColor: + type: string + description: A six-character hexadecimal colour code + icon: + type: string + description: A FontAwesome icon string + createtimeISO: + type: string + description: "`createtime` rendered as an ISO 8601 format" + cover:thumb:url: + type: string + cover:url: + type: string + cover:position: + type: string + descriptionParsed: + type: string + members: + type: array + items: + $ref: UserObject.yaml#/UserObjectSlim + membersNextStart: + type: number + pending: + type: array + invited: + type: array + isMember: + type: boolean + isPending: + type: boolean + isInvited: + type: boolean + isOwner: + type: boolean + nullable: true +GroupDataObject: + type: object + description: The response from an internal call to `Groups.getGroupData(, [])` with **explicitly** no fields passed in + properties: + name: + type: string + description: The group name + slug: + type: string + description: URL-safe slug of the group name + createtime: + type: number + description: UNIX timestamp of the group's creation + userTitle: + type: number + description: Label text for the user badge + userTitleEnabled: + type: number + description: + type: string + description: The group description + memberCount: + type: number + hidden: + type: number + system: + type: number + private: + type: number + disableJoinRequests: + type: number + disableLeave: + type: number + cover:url: + type: string + cover:thumb:url: + type: string + nameEncoded: + type: string + displayName: + type: string + description: A custom override of the group's name, a friendly name + labelColor: + type: string + description: A six-character hexadecimal colour code + textColor: + type: string + description: A six-character hexadecimal colour code + icon: + type: string + description: A FontAwesome icon string + createtimeISO: + type: string + description: "`createtime` rendered as an ISO 8601 format" + cover:position: + type: string \ No newline at end of file diff --git a/public/openapi/components/schemas/Pagination.yaml b/public/openapi/components/schemas/Pagination.yaml new file mode 100644 index 0000000000..290eb95971 --- /dev/null +++ b/public/openapi/components/schemas/Pagination.yaml @@ -0,0 +1,64 @@ +Pagination: + type: object + properties: + pagination: + type: object + properties: + prev: + type: object + properties: + page: + type: number + active: + type: boolean + next: + type: object + properties: + page: + type: number + active: + type: boolean + first: + type: object + properties: + page: + type: number + active: + type: boolean + last: + type: object + properties: + page: + type: number + active: + type: boolean + rel: + type: array + description: A collection of objects used to build the link tags pointing to adjacent pages, if any. + items: + type: object + properties: + rel: + type: string + enum: [prev, next] + href: + type: string + description: A query string that points to the previous or next page + pages: + type: array + items: + type: object + properties: + page: + type: number + description: The current page + active: + type: boolean + description: If the page noted in this array is the current page + qs: + type: string + description: A query string that points to the page noted in this array + currentPage: + type: number + pageCount: + type: number \ No newline at end of file diff --git a/public/openapi/components/schemas/PostsObject.yaml b/public/openapi/components/schemas/PostsObject.yaml new file mode 100644 index 0000000000..1962a9a71f --- /dev/null +++ b/public/openapi/components/schemas/PostsObject.yaml @@ -0,0 +1,115 @@ +PostsObject: + type: array + items: + type: object + properties: + pid: + type: number + tid: + type: number + description: A topic identifier + content: + type: string + uid: + type: number + description: A user identifier + timestamp: + type: number + deleted: + type: boolean + upvotes: + type: number + downvotes: + type: number + votes: + type: number + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + nullable: true + status: + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + topic: + type: object + properties: + uid: + type: number + description: A user identifier + tid: + type: number + description: A topic identifier + title: + type: string + cid: + type: number + description: A category identifier + slug: + type: string + deleted: + type: number + postcount: + type: number + mainPid: + type: number + description: The post id of the first post in this topic (also called the + "original post") + teaserPid: + type: number + description: The post id of the teaser (the most recent post, depending on settings) + nullable: true + titleRaw: + type: string + category: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + icon: + type: string + slug: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + bgColor: + type: string + color: + type: string + image: + nullable: true + imageClass: + nullable: true + type: string + isMainPost: + type: boolean \ No newline at end of file diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml new file mode 100644 index 0000000000..9521050915 --- /dev/null +++ b/public/openapi/components/schemas/TopicObject.yaml @@ -0,0 +1,253 @@ +TopicObject: + type: object + properties: + tid: + type: number + description: A topic identifier + uid: + type: number + description: A user identifier + cid: + type: number + description: A category identifier + mainPid: + type: number + description: The post id of the first post in this topic (also called the + "original post") + title: + type: string + slug: + type: string + timestamp: + type: number + lastposttime: + type: number + postcount: + type: number + viewcount: + type: number + teaserPid: + oneOf: + - type: number + - type: string + nullable: true + upvotes: + type: number + downvotes: + type: number + deleted: + type: number + locked: + type: number + pinned: + type: number + description: Whether or not this particular topic is pinned to the top of the + category + deleterUid: + type: number + titleRaw: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + lastposttimeISO: + type: string + votes: + type: number + category: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + slug: + type: string + icon: + type: string + image: + nullable: true + type: string + imageClass: + nullable: true + type: string + bgColor: + type: string + color: + type: string + disabled: + type: number + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + fullname: + type: string + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + reputation: + type: number + postcount: + type: number + picture: + type: string + nullable: true + signature: + type: string + nullable: true + banned: + type: number + status: + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + banned_until_readable: + type: string + required: + - uid + - username + - userslug + - reputation + - postcount + - picture + - signature + - banned + - status + - icon:text + - icon:bgColor + - banned_until_readable + teaser: + type: object + properties: + pid: + type: number + uid: + type: number + description: A user identifier + timestamp: + type: number + tid: + type: number + description: A topic identifier + content: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users + without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's + auto-generated icon + example: "#f44336" + index: + type: number + nullable: true + tags: + type: array + items: + type: object + properties: + value: + type: string + valueEscaped: + type: string + color: + type: string + bgColor: + type: string + score: + type: number + isOwner: + type: boolean + ignored: + type: boolean + unread: + type: boolean + bookmark: + nullable: true + type: number + unreplied: + type: boolean + icons: + type: array + items: + type: string + description: HTML injected into the theme + index: + type: number + thumb: + type: string + required: + - tid + - uid + - cid + - mainPid + - title + - slug + - timestamp + - lastposttime + - postcount + - viewcount + - teaserPid + - upvotes + - downvotes + - deleted + - locked + - pinned + - deleterUid + - titleRaw + - timestampISO + - lastposttimeISO + - votes + - category + - user + - teaser + - tags + - isOwner + - ignored + - unread + - bookmark + - unreplied + - icons + - index \ No newline at end of file diff --git a/public/openapi/components/schemas/UserObject.yaml b/public/openapi/components/schemas/UserObject.yaml new file mode 100644 index 0000000000..1509a8ef54 --- /dev/null +++ b/public/openapi/components/schemas/UserObject.yaml @@ -0,0 +1,608 @@ +UserObject: + type: object + properties: + uid: + type: number + description: A user identifier + example: 1 + username: + type: string + description: A friendly name for a given user account + example: Dragon Fruit + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces removed, etc.) + example: dragon-fruit + email: + type: string + description: Email address associated with the user account + example: dragonfruit@example.org + 'email:confirmed': + type: number + description: Whether the user has confirmed their email address or not + example: 1 + joindate: + type: number + description: A UNIX timestamp representing the moment the user's account was created + example: 1585337827953 + lastonline: + type: number + description: A UNIX timestamp representing the moment the user was last recorded online on this site + example: 1585337827953 + picture: + type: string + description: A URL pointing to a picture to be used as the user's avatar + example: 'https://images.unsplash.com/photo-1560070094-e1f2ddec4337?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=256&h=256&q=80' + nullable: true + fullname: + type: string + example: Mr. Dragon Fruit Jr. + location: + type: string + example: 'Toronto, Canada' + nullable: true + birthday: + type: string + description: A birthdate given in an ISO format parseable by the Date object + example: 03/27/2020 + nullable: true + website: + type: string + example: 'https://example.org' + nullable: true + aboutme: + type: string + example: | + This is a paragraph all about how my life got twist-turned upside-down + and I'd like to take a minute and sit right here, + to tell you all about how I became the administrator of NodeBB + nullable: true + signature: + type: string + example: | + This is an example signature + It can span multiple lines. + nullable: true + uploadedpicture: + type: string + example: /assets/profile/1-profileimg.png + description: 'In almost all cases, defer to "picture" instead. Use this if you need to specifically reference the picture uploaded to the forum.' + nullable: true + profileviews: + type: number + description: The number of times this user's profile has been viewed + example: 1000 + reputation: + type: number + description: The user's reputation score on the forum. Out-of-the-box, users gain/lose reputation points based on upvotes/downvotes, though plugins can alter the logic and criterion for awarding reputation points + example: 100 + postcount: + type: number + example: 1000 + topiccount: + type: number + example: 50 + lastposttime: + type: number + description: A UNIX timestamp representing the moment the user posted last + example: 1585337827953 + banned: + type: number + description: A Boolean representing whether a user is banned or not + example: 0 + 'banned:expire': + type: number + description: A UNIX timestamp representing the moment the ban will be lifted + example: 1585337827953 + status: + type: string + enum: + - online + - offline + - dnd + - away + example: online + flags: + type: number + example: 0 + nullable: true + followerCount: + type: number + example: 2 + followingCount: + type: number + example: 5 + 'cover:url': + type: string + example: /assets/profile/1-cover.png + nullable: true + 'cover:position': + type: string + example: 50.0301% 19.2464% + nullable: true + groupTitle: + type: string + example: '["administrators","Staff"]' + groupTitleArray: + type: array + example: + - administrators + - Staff + 'icon:text': + type: string + description: A single-letter representation of a username. This is used in the auto-generated icon given to users without an avatar + example: D + 'icon:bgColor': + type: string + description: A six-character hexadecimal colour code assigned to the user. This value is used in conjunction with `icon:text` for the user's auto-generated icon + example: '#9c27b0' + joindateISO: + type: string + example: '2020-03-27T20:30:36.590Z' + lastonlineISO: + type: string + example: '2020-03-27T20:30:36.590Z' + banned_until: + type: number + description: A UNIX timestamp representing the moment a ban will be lifted + example: 0 + banned_until_readable: + type: string + description: An ISO 8601 formatted date string representing the moment a ban will be lifted, or the words "Not Banned" + example: Not Banned + required: + - uid + - username + - userslug + - 'email:confirmed' + - joindate + - lastonline + - picture + - location + - birthday + - website + - aboutme + - signature + - uploadedpicture + - profileviews + - reputation + - postcount + - topiccount + - lastposttime + - banned + - 'banned:expire' + - status + - enum + - flags + - followerCount + - followingCount + - 'cover:url' + - 'cover:position' + - groupTitle + - groupTitleArray + - example + - 'icon:text' + - 'icon:bgColor' + - joindateISO + - lastonlineISO + - banned_until + - banned_until_readable +UserObjectFull: + # accountHelpers.getUserDataByUserSlug + type: object + properties: + uid: + type: number + description: A user identifier + example: 1 + username: + type: string + description: A friendly name for a given user account + example: Dragon Fruit + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces removed, etc.) + example: dragon-fruit + email: + type: string + description: Email address associated with the user account + example: dragonfruit@example.org + 'email:confirmed': + type: number + description: Whether the user has confirmed their email address or not + example: 1 + joindate: + type: number + description: A UNIX timestamp representing the moment the user's account was created + example: 1585337827953 + lastonline: + type: number + description: A UNIX timestamp representing the moment the user was last recorded online on this site + example: 1585337827953 + picture: + type: string + description: A URL pointing to a picture to be used as the user's avatar + example: 'https://images.unsplash.com/photo-1560070094-e1f2ddec4337?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=256&h=256&q=80' + nullable: true + fullname: + type: string + example: Mr. Dragon Fruit Jr. + location: + type: string + example: 'Toronto, Canada' + birthday: + type: string + description: A birthdate given in an ISO format parseable by the Date object + example: 03/27/2020 + website: + type: string + example: 'https://example.org' + aboutme: + type: string + example: | + This is a paragraph all about how my life got twist-turned upside-down + and I'd like to take a minute and sit right here, + to tell you all about how I became the administrator of NodeBB + signature: + type: string + example: | + This is an example signature + It can span multiple lines. + uploadedpicture: + type: string + example: /assets/profile/1-profileimg.png + description: 'In almost all cases, defer to "picture" instead. Use this if you need to specifically reference the picture uploaded to the forum.' + nullable: true + profileviews: + type: number + description: The number of times this user's profile has been viewed + example: 1000 + reputation: + type: number + description: The user's reputation score on the forum. Out-of-the-box, users gain/lose reputation points based on upvotes/downvotes, though plugins can alter the logic and criterion for awarding reputation points + example: 100 + postcount: + type: number + example: 1000 + topiccount: + type: number + example: 50 + lastposttime: + type: number + description: A UNIX timestamp representing the moment the user posted last + example: 1585337827953 + banned: + type: number + description: A Boolean representing whether a user is banned or not + example: 0 + 'banned:expire': + type: number + description: A UNIX timestamp representing the moment the ban will be lifted + example: 1585337827953 + status: + type: string + enum: + - online + - offline + - dnd + - away + example: online + flags: + type: number + example: 0 + nullable: true + followerCount: + type: number + example: 2 + followingCount: + type: number + example: 5 + 'cover:url': + type: string + example: /assets/profile/1-cover.png + nullable: true + 'cover:position': + type: string + example: 50.0301% 19.2464% + nullable: true + groupTitle: + type: string + example: '["administrators","Staff"]' + groupTitleArray: + type: array + example: + - administrators + - Staff + 'icon:text': + type: string + description: A single-letter representation of a username. This is used in the auto-generated icon given to users without an avatar + example: D + 'icon:bgColor': + type: string + description: A six-character hexadecimal colour code assigned to the user. This value is used in conjunction with `icon:text` for the user's auto-generated icon + example: '#9c27b0' + joindateISO: + type: string + example: '2020-03-27T20:30:36.590Z' + lastonlineISO: + type: string + example: '2020-03-27T20:30:36.590Z' + banned_until: + type: number + description: A UNIX timestamp representing the moment a ban will be lifted + example: 0 + banned_until_readable: + type: string + description: An ISO 8601 formatted date string representing the moment a ban will be lifted, or the words "Not Banned" + example: Not Banned + aboutmeParsed: + type: string + age: + type: number + emailClass: + type: string + ips: + type: array + items: + type: string + moderationNote: + type: string + isBlocked: + type: boolean + blocksCount: + type: number + yourid: + type: number + theirid: + type: number + isTargetAdmin: + type: boolean + isAdmin: + type: boolean + isGlobalModerator: + type: boolean + isModerator: + type: boolean + isAdminOrGlobalModerator: + type: boolean + isAdminOrGlobalModeratorOrModerator: + type: boolean + isSelfOrAdminOrGlobalModerator: + type: boolean + canEdit: + type: boolean + canBan: + type: boolean + canChangePassword: + type: boolean + isSelf: + type: boolean + isFollowing: + type: boolean + hasPrivateChat: + type: number + showHidden: + type: boolean + groups: + type: array + items: + $ref: ./GroupObject.yaml#/GroupFullObject + disableSignatures: + type: boolean + reputation:disabled: + type: boolean + downvote:disabled: + type: boolean + profile_links: + type: array + items: + type: object + properties: + id: + type: string + route: + type: string + name: + type: string + visibility: + type: object + properties: + self: + type: boolean + other: + type: boolean + moderator: + type: boolean + globalMod: + type: boolean + admin: + type: boolean + canViewInfo: + type: boolean + public: + type: boolean + icon: + type: string + required: + - id + - route + - name + - visibility + - public + sso: + type: array + items: + type: object + properties: + associated: + type: boolean + url: + type: string + name: + type: string + icon: + type: string + deauthUrl: + type: string + websiteLink: + type: string + websiteName: + type: string + username:disableEdit: + type: number + email:disableEdit: + type: number +UserObjectSlim: + type: object + properties: + uid: + type: number + description: A user identifier + example: 1 + username: + type: string + description: A friendly name for a given user account + example: Dragon Fruit + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces removed, etc.) + example: dragon-fruit + picture: + type: string + description: A URL pointing to a picture to be used as the user's avatar + example: 'https://images.unsplash.com/photo-1560070094-e1f2ddec4337?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=256&h=256&q=80' + nullable: true + status: + type: string + enum: + - online + - offline + - dnd + - away + example: online + postcount: + type: number + example: 1000 + reputation: + type: number + description: The user's reputation score on the forum. Out-of-the-box, users gain/lose reputation points based on upvotes/downvotes, though plugins can alter the logic and criterion for awarding reputation points + example: 100 + 'email:confirmed': + type: number + description: Whether the user has confirmed their email address or not + example: 1 + lastonline: + type: number + description: A UNIX timestamp representing the moment the user was last recorded online on this site + example: 1585337827953 + flags: + type: number + example: 0 + nullable: true + banned: + type: number + description: A Boolean representing whether a user is banned or not + example: 0 + 'banned:expire': + type: number + description: A UNIX timestamp representing the moment the ban will be lifted + example: 1585337827953 + joindate: + type: number + description: A UNIX timestamp representing the moment the user's account was created + example: 1585337827953 + 'icon:text': + type: string + description: A single-letter representation of a username. This is used in the auto-generated icon given to users without an avatar + example: D + 'icon:bgColor': + type: string + description: A six-character hexadecimal colour code assigned to the user. This value is used in conjunction with `icon:text` for the user's auto-generated icon + example: '#9c27b0' + joindateISO: + type: string + example: '2020-03-27T20:30:36.590Z' + lastonlineISO: + type: string + example: '2020-03-27T20:30:36.590Z' + banned_until: + type: number + description: A UNIX timestamp representing the moment a ban will be lifted + example: 0 + banned_until_readable: + type: string + description: An ISO 8601 formatted date string representing the moment a ban will be lifted, or the words "Not Banned" + example: Not Banned + administrator: + type: boolean +UserObjectACP: + type: object + properties: + uid: + type: number + description: A user identifier + example: 1 + username: + type: string + description: A friendly name for a given user account + example: Dragon Fruit + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces removed, etc.) + example: dragon-fruit + email: + type: string + description: Email address associated with the user account + example: dragonfruit@example.org + postcount: + type: number + example: 1000 + joindate: + type: number + description: A UNIX timestamp representing the moment the user's account was created + example: 1585337827953 + banned: + type: number + description: A Boolean representing whether a user is banned or not + example: 0 + reputation: + type: number + description: The user's reputation score on the forum. Out-of-the-box, users gain/lose reputation points based on upvotes/downvotes, though plugins can alter the logic and criterion for awarding reputation points + example: 100 + picture: + type: string + description: A URL pointing to a picture to be used as the user's avatar + example: 'https://images.unsplash.com/photo-1560070094-e1f2ddec4337?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=256&h=256&q=80' + nullable: true + flags: + type: number + example: 0 + nullable: true + lastonline: + type: number + description: A UNIX timestamp representing the moment the user was last recorded online on this site + example: 1585337827953 + 'email:confirmed': + type: number + description: Whether the user has confirmed their email address or not + example: 1 + 'icon:text': + type: string + description: A single-letter representation of a username. This is used in the auto-generated icon given to users without an avatar + example: D + 'icon:bgColor': + type: string + description: A six-character hexadecimal colour code assigned to the user. This value is used in conjunction with `icon:text` for the user's auto-generated icon + example: '#9c27b0' + joindateISO: + type: string + example: '2020-03-27T20:30:36.590Z' + lastonlineISO: + type: string + example: '2020-03-27T20:30:36.590Z' + banned_until_readable: + type: string + description: An ISO 8601 formatted date string representing the moment a ban will be lifted, or the words "Not Banned" + example: Not Banned + administrator: + type: boolean diff --git a/public/openapi/read.yaml b/public/openapi/read.yaml new file mode 100644 index 0000000000..f5ab877d9d --- /dev/null +++ b/public/openapi/read.yaml @@ -0,0 +1,8381 @@ +openapi: 3.0.0 +info: + title: nodebb + version: 1.13.2 + license: + name: GPL-3.0 + description: >- + # Overview + + The following document outlines every Read API route available via NodeBB. Unlike the write API, the v1.x API was coded organically, and is **not** strictly RESTful. These shortcomings will be addressed in time as the APIs mature. + + ## Shortcomings + + The Read API is named because its primary use is by NodeBB itself when navigating between pages. Therefore, the routes almost universally always follow the same path as actual pages on NodeBB itself. There are also a small number of non-`GET` routes, which doesn't necessarily make sense in a Read API. These will be merged into the Write API in time. + + ## Authentication + + ### Cookie Authentication + + This default authentication behaviour of this API is via cookie jar to find a valid session. A valid login session is required for API calls that pertain to operations involving a logged-in user. For example, `/api/unread` is a route showing unread topics, and is not accessible by guest users. + + ### Bearer Authentcation + + The Write API offers bearer authentication, as administered through the administration panel. + + * For NodeBB v1.x, this is provided by [`nodebb-plugin-write-api`](https://github.com/NodeBB/nodebb-plugin-write-api). + * For NodeBB v2.x+ (in development), the Write API is available in core, and bearer authentication is available out-of-the-box + + ### JSON Web Token (JWT) + + The Write API also consumes valid JWTs as payload bodies, when signed with a server-generated key. The same restrictions apply as above, with Bearer Authentication (re: NodeBB v1.x vs v2.x). +tags: + - name: home + description: Routes used at the forum index only + - name: categories + description: Category hierarchy and navigation + - name: topics + - name: posts + - name: users + - name: authentication + description: User authentication (e.g. login/registration) + - name: groups + description: User groups + - name: admin + description: Administrative Control Panel (ACP) routing + - name: emails + description: Email utilities + - name: flags + description: Reporting of content by users + - name: notifications + description: Real-time notifications + - name: search + - name: tags + description: Disparate method of categorizing topics + - name: shorthand + description: Convenience and utility routes for accessing other part of the API +paths: + /api/: + get: + tags: + - home + description: > + This route is used to populate the homepage of NodeBB. It is the main + access point of the forum, and shows a list of categories for navigation + purposes. + summary: Get forum index data + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + title: + type: string + description: The page title + categories: + description: A collection of category data objects + type: array + items: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + children: + type: array + items: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + children: + type: array + items: + $ref: components/schemas/CategoryObject.yaml#/CategoryObject + parent: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + posts: + type: array + items: + type: object + properties: + pid: + type: number + timestamp: + type: number + content: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to + users without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction + with `icon:text` for the user's + auto-generated icon + example: "#f44336" + index: + type: number + cid: + type: number + description: A category identifier + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + topic: + type: object + properties: + slug: + type: string + title: + type: string + imageClass: + type: string + timesClicked: + type: number + posts: + type: array + items: + type: object + properties: + pid: + type: number + timestamp: + type: number + content: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users + without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's + auto-generated icon + example: "#f44336" + index: + type: number + cid: + type: number + description: A category identifier + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + topic: + type: object + properties: + slug: + type: string + title: + type: string + teaser: + type: object + properties: + url: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + pid: + type: number + topic: + type: object + properties: + slug: + type: string + title: + type: string + imageClass: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin: + get: + tags: + - admin + summary: /api/admin + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | This route is identical to /api/admin/general/dashboard. When the routes are split into separate files, replace this definition with a $ref to that route" + /api/admin/general/dashboard: + get: + tags: + - admin + summary: Get administrative dashboard + responses: + "200": + description: A JSON object containing dashboard data + content: + application/json: + schema: + allOf: + - type: object + properties: + version: + type: string + lookupFailed: + type: boolean + latestVersion: + type: string + nullable: true + upgradeAvailable: + type: boolean + nullable: true + currentPrerelease: + type: boolean + notices: + type: array + items: + type: object + properties: + done: + type: boolean + doneText: + type: string + notDoneText: + type: string + tooltip: + type: string + link: + type: string + required: + - done + stats: + type: array + items: + type: object + properties: + yesterday: + type: number + today: + type: number + lastweek: + type: number + thisweek: + type: number + lastmonth: + type: number + thismonth: + type: number + alltime: + type: number + dayIncrease: + type: string + dayTextClass: + type: string + weekIncrease: + type: string + weekTextClass: + type: string + monthIncrease: + type: string + monthTextClass: + type: string + name: + type: string + canRestart: + type: boolean + lastrestart: + nullable: true + type: object + properties: + uid: + type: number + description: A user identifier + ip: + type: string + timestamp: + type: number + user: + $ref: components/schemas/UserObject.yaml#/UserObject + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/general/languages: + get: + tags: + - admin + summary: Get language settings + responses: + "200": + description: A JSON object containing available languages and settings + content: + application/json: + schema: + allOf: + - type: object + properties: + languages: + type: array + items: + type: object + properties: + name: + type: string + description: Localised name of the language + code: + type: string + description: A language code (similar to ISO-639) + dir: + type: string + description: Directionality of the language + enum: [ltr, rtl] + selected: + type: boolean + description: Denotes the currently selected default system language on the forum + autoDetectLang: + type: integer + description: Whether the forum will attempt to guess language based on browser's `Accept-Language` header + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/general/sounds: + get: + tags: + - admin + summary: Get sound settings + responses: + "200": + description: A JSON object containing available sounds and settings + content: + application/json: + schema: + allOf: + - type: object + properties: + notification-sound: + type: array + items: + type: object + properties: + name: + type: string + sounds: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + selected: + type: boolean + chat-incoming-sound: + type: array + items: + type: object + properties: + name: + type: string + sounds: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + selected: + type: boolean + chat-outgoing-sound: + type: array + items: + type: object + properties: + name: + type: string + sounds: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + selected: + type: boolean + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/general/navigation: + get: + tags: + - admin + summary: Get navigation bar settings + responses: + "200": + description: A JSON object containing navigation settings + content: + application/json: + schema: + allOf: + - type: object + properties: + enabled: + type: array + items: + type: object + properties: + route: + type: string + description: Relative URL to the page the navigation item goes to + title: + type: string + description: Tooltip text + enabled: + type: boolean + iconClass: + type: string + description: A FontAwesome icon string + textClass: + type: string + description: HTML class applied to the text label for this navigation item + text: + type: string + description: Label text for this navigation item + order: + type: integer + description: Ordinality of this item, lower value appears earlier + groups: + type: array + items: + type: object + properties: + displayName: + type: string + selected: + type: boolean + index: + type: integer + description: Seemingly identical to order, but an integer instead of a string + selected: + type: boolean + available: + type: array + items: + type: object + properties: + id: + type: string + description: Unique ID that will be added to the navigation element's `id` property in the DOM + route: + type: string + description: Relative URL to the page the navigation item goes to + title: + type: string + description: Tooltip text + enabled: + type: boolean + iconClass: + type: string + description: A FontAwesome icon string + textClass: + type: string + description: HTML class applied to the text label for this navigation item + text: + type: string + description: Label text for this navigation item + core: + type: boolean + description: Whether the navigation item is provided by core or not (a plugin) + groups: + type: array + items: + type: object + properties: + name: + type: string + displayName: + type: string + properties: + type: object + properties: + targetBlank: + type: boolean + groups: + type: array + items: + type: object + properties: + name: + type: string + displayName: + type: string + navigation: + type: array + description: A clone of `enabled` + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/general/homepage: + get: + tags: + - admin + summary: /api/admin/general/homepage + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + routes: + type: array + items: + type: object + properties: + route: + type: string + name: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/general/social: + get: + tags: + - admin + summary: Get post social sharing settings + responses: + "200": + description: "A JSON object containing post social sharing settings" + content: + application/json: + schema: + allOf: + - type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + class: + type: string + description: A FontAwesome icon string + activated: + type: boolean + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/categories: + get: + tags: + - admin + summary: /api/admin/manage/categories + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/admin/manage/categories/{category_id}": + get: + tags: + - admin + summary: /api/admin/manage/categories/{category_id} + parameters: + - name: category_id + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + category: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + parent: + $ref: components/schemas/CategoryObject.yaml#/CategoryObject + allCategories: + type: array + items: + type: object + properties: + text: + type: string + value: + type: number + selected: + type: boolean + customClasses: + type: array + items: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/admin/manage/categories/{category_id}/analytics": + get: + tags: + - admin + summary: /api/admin/manage/categories/{category_id}/analytics + parameters: + - name: category_id + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + name: + type: string + analytics: + type: object + properties: + pageviews:hourly: + type: array + items: + type: number + pageviews:daily: + type: array + items: + type: number + topics:daily: + type: array + items: + type: number + posts:daily: + type: array + items: + type: number + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/admin/manage/privileges/{cid}": + get: + tags: + - admin + summary: Get category privileges + parameters: + - name: cid + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + privileges: + type: object + properties: + labels: + type: object + properties: + users: + type: array + items: + type: object + properties: + name: + type: string + groups: + type: array + items: + type: object + properties: + name: + type: string + users: + type: array + items: + type: object + properties: + name: + type: string + nameEscaped: + type: string + privileges: + type: object + additionalProperties: + type: boolean + description: Each privilege will have a key in this object + groups: + type: array + items: + type: object + properties: + name: + type: string + nameEscaped: + type: string + privileges: + type: object + additionalProperties: + type: boolean + description: Each privilege will have a key in this object + isPrivate: + type: boolean + columnCountUser: + type: number + columnCountUserOther: + type: number + columnCountGroup: + type: number + columnCountGroupOther: + type: number + categories: + type: array + items: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + icon: + type: string + selected: + type: boolean + level: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + imageClass: + type: string + required: + - cid + - name + - icon + - selected + selectedCategory: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + imageClass: + type: string + selected: + type: boolean + cid: + type: number + description: A category identifier + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/tags: + get: + tags: + - admin + summary: Get tag settings + responses: + "200": + description: "A JSON object containing tag settings" + content: + application/json: + schema: + allOf: + - type: object + properties: + tags: + type: array + items: + type: object + properties: + value: + type: string + description: The tag name + score: + type: number + description: The number of topics containing this tag + valueEscaped: + type: string + color: + type: string + description: Six-character hexadecimal string (with `#` prepended) + example: "#ff0000" + bgColor: + type: string + description: Six-character hexadecimal string (with `#` prepended) + example: "#ff0000" + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/post-queue: + get: + tags: + - admin + summary: Get post queue settings + responses: + "200": + description: "A JSON object containing post queue settings" + content: + application/json: + schema: + allOf: + - type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: string + description: Unique ID given to this queued post + uid: + type: number + description: A user identifier + data: + type: object + description: Queued post data + properties: + title: + type: string + content: + type: string + thumb: + type: string + cid: + type: number + tags: + type: array + uid: + type: number + req: + type: object + properties: + uid: + type: number + ip: + type: string + host: + type: string + protocol: + type: string + secure: + type: boolean + url: + type: string + path: + type: string + headers: + type: object + properties: + host: + type: string + user-agent: + type: string + accept: + type: string + accept-language: + type: string + accept-encoding: + type: string + referer: + type: string + dnt: + type: string + connection: + type: string + cookie: + type: string + pragma: + type: string + cache-control: + type: string + timestamp: + type: number + fromQueue: + type: boolean + timestampISO: + type: string + rawContent: + type: string + type: + type: string + user: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + topic: + type: object + properties: + title: + type: number + cid: + type: number + category: + $ref: components/schemas/CategoryObject.yaml#/CategoryObject + title: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/ip-blacklist: + get: + tags: + - admin + summary: Get IP blacklist settings + responses: + "200": + description: "A JSON object containing IP blacklist settings" + content: + application/json: + schema: + allOf: + - type: object + properties: + title: + type: string + rules: + type: string + description: A plain text string containing blocked IPs or IP ranges. Separate entries are separated by a newline (`\n`) + nullable: true + analytics: + type: object + properties: + daily: + type: array + description: The values here are shown in ascending order from 6 days ago, to the current day + items: + type: number + description: The number of requests blocked per day + hourly: + type: array + description: The values here are shown in ascending order from 23 hours ago, to the current hour + items: + type: number + description: The number of requests blocked per hour + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/users: + get: + tags: + - admin + summary: /api/admin/manage/users + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + users: + type: array + items: + $ref: components/schemas/UserObject.yaml#/UserObjectACP + page: + type: number + pageCount: + type: number + resultsPerPage: + type: number + latest: + type: boolean + search_display: + type: string + requireEmailConfirmation: + type: number + inviteOnly: + type: boolean + adminInviteOnly: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/users/search: + get: + tags: + - admin + summary: /api/admin/manage/users/search + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + search_display: + type: string + users: + type: array + items: + $ref: components/schemas/UserObject.yaml#/UserObjectACP + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/users/latest: + get: + tags: + - admin + summary: /api/admin/manage/users/latest + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/users/not-validated: + get: + tags: + - admin + summary: /api/admin/manage/users/not-validated + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/users/no-posts: + get: + tags: + - admin + summary: /api/admin/manage/users/no-posts + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/users/top-posters: + get: + tags: + - admin + summary: /api/admin/manage/users/top-posters + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/users/most-reputation: + get: + tags: + - admin + summary: /api/admin/manage/users/most-reputation + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/users/inactive: + get: + tags: + - admin + summary: /api/admin/manage/users/inactive + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/users/flagged: + get: + tags: + - admin + summary: /api/admin/manage/users/flagged + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/users/banned: + get: + tags: + - admin + summary: /api/admin/manage/users/banned + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest" + /api/admin/manage/registration: + get: + tags: + - admin + summary: Get registration queue/invites + responses: + "200": + description: "A JSON object containing the registration queue and invites" + content: + application/json: + schema: + allOf: + - type: object + properties: + registrationQueueCount: + type: number + users: + type: array + items: + type: object + properties: + username: + type: string + email: + type: string + ip: + type: string + timestampISO: + type: string + usernameEscaped: + type: string + ipMatch: + type: array + items: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + customActions: + type: array + items: + type: object + properties: + title: + type: string + id: + type: string + class: + type: string + icon: + type: string + customHeaders: + type: array + invites: + type: array + items: + type: object + properties: + uid: + type: number + invitations: + type: array + items: + type: object + properties: + email: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/admins-mods: + get: + tags: + - admin + summary: Get administrators and moderators + responses: + "200": + description: "A JSON object containing administrators and moderators globally and per-category" + content: + application/json: + schema: + allOf: + - type: object + properties: + admins: + $ref: components/schemas/GroupObject.yaml#/GroupFullObject + globalMods: + $ref: components/schemas/GroupObject.yaml#/GroupFullObject + categories: + type: array + items: + type: object + properties: + cid: + type: number + name: + type: string + level: + type: number + example: 0 + icon: + type: string + description: A FontAwesome icon string + parentCid: + type: number + description: The parent category's identifier + color: + type: string + description: A six-character hexadecimal colour code + bgColor: + type: string + description: A six-character hexadecimal colour code + imageClass: + type: string + depth: + type: number + description: The depth of the category relative to the forum root (`0` is root level) + moderators: + type: array + items: + $ref: components/schemas/UserObject.yaml#/UserObjectSlim + allPrivileges: + type: array + items: + type: string + description: A simple array containing user privilege names (used client-side when giving mod privilege) + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/groups: + get: + tags: + - admin + summary: /api/admin/manage/groups + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + groups: + type: array + items: + type: object + properties: + name: + type: string + description: + type: string + deleted: + oneOf: + - type: number + - type: string + hidden: + type: number + system: + type: number + userTitle: + type: string + icon: + type: string + labelColor: + type: string + slug: + type: string + createtime: + type: number + memberCount: + type: number + private: + type: number + cover:url: + type: string + cover:position: + type: string + userTitleEnabled: + type: number + disableJoinRequests: + type: number + disableLeave: + type: number + nameEncoded: + type: string + displayName: + type: string + textColor: + type: string + createtimeISO: + type: string + cover:thumb:url: + type: string + ownerUid: + type: number + required: + - name + - description + - hidden + - system + - userTitle + - icon + - labelColor + - slug + - createtime + - memberCount + - private + - cover:url + - cover:position + - userTitleEnabled + - disableJoinRequests + - disableLeave + - nameEncoded + - displayName + - textColor + - createtimeISO + - cover:thumb:url + yourid: + type: number + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/admin/manage/groups/{name}": + get: + tags: + - admin + summary: /api/admin/manage/groups/{name} + parameters: + - name: name + in: path + required: true + schema: + type: string + example: administrators + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + group: + $ref: components/schemas/GroupObject.yaml#/GroupFullObject + groupNames: + type: array + items: + type: object + properties: + encodedName: + type: string + displayName: + type: string + selected: + type: boolean + allowPrivateGroups: + type: number + maximumGroupNameLength: + type: number + maximumGroupTitleLength: + type: number + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/uploads: + get: + tags: + - admin + summary: Get uploaded files + parameters: + - in: query + name: dir + schema: + type: string + description: Path of the folder, relative to `public/uploads/` + example: / + responses: + "200": + description: "A JSON object containing uploaded files" + content: + application/json: + schema: + allOf: + - type: object + properties: + currentFolder: + type: string + description: Path of the folder, relative to `public/uploads/` + showPids: + type: boolean + description: Whether or not the post identifiers should be shown (this is `true` only for `public/uploads/files/`, as that is where post uploads go) + files: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + description: Path relative to `currentFolder` + url: + type: string + description: Relative URL ready to be combined with `config.relative_path` on the client-side or templates + fileCount: + type: number + description: For directories, the number of files inside + size: + type: number + description: The size of the file/directory + sizeHumanReadable: + type: string + isDirectory: + type: boolean + isFile: + type: boolean + mtime: + type: number + description: Last modified time of the file, down to the microsecond (expressed as a UNIX timestamp) + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/manage/digest: + get: + tags: + - admin + summary: Get system digest info/settings + responses: + "200": + description: "A JSON object containing recent digest sends and settings" + content: + application/json: + schema: + allOf: + - type: object + properties: + title: + type: string + delivery: + type: array + items: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + picture: + nullable: true + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without an + avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with `icon:text` + for the user's auto-generated icon + example: "#f44336" + lastDelivery: + type: string + setting: + type: boolean + default: + type: string + required: + - title + - delivery + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/admin/settings/{term}": + get: + tags: + - admin + summary: Get system settings + parameters: + - name: term + in: path + required: true + schema: + type: string + example: general + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: {} + additionalProperties: + type: object + description: Most of the settings pages have their values loaded on the client-side, so the settings are not exposed server-side. + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/admin/appearance/{term}": + get: + tags: + - admin + summary: Get appearance settings + parameters: + - name: term + in: path + required: true + schema: + type: string + example: themes + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/extend/plugins: + get: + tags: + - admin + summary: /api/admin/extend/plugins + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + installed: + type: array + items: + type: object + properties: + latest: + type: string + description: + type: string + name: + type: string + updated: + type: string + url: + type: string + numInstalls: + type: number + isCompatible: + type: boolean + id: + type: string + installed: + type: boolean + active: + type: boolean + isTheme: + type: boolean + error: + type: boolean + version: + type: string + license: + type: object + properties: + name: + type: string + text: + type: string + nullable: true + outdated: + type: boolean + settingsRoute: + type: string + required: + - latest + - description + - name + - id + - installed + - active + - isTheme + - error + - version + - license + - outdated + installedCount: + type: number + activeCount: + type: number + inactiveCount: + type: number + upgradeCount: + type: number + download: + type: array + items: + type: object + properties: + name: + type: string + updated: + type: string + description: + type: string + latest: + type: string + url: + type: string + numInstalls: + type: number + isCompatible: + type: boolean + id: + type: string + installed: + type: boolean + active: + type: boolean + required: + - name + - updated + - latest + - url + - numInstalls + - isCompatible + - id + - installed + - active + incompatible: + type: array + items: + type: object + properties: + latest: + type: string + description: + type: string + name: + type: string + updated: + type: string + url: + type: string + numInstalls: + type: number + isCompatible: + type: boolean + id: + type: string + installed: + type: boolean + active: + type: boolean + required: + - name + - updated + - latest + - url + - numInstalls + - isCompatible + - id + - installed + - active + submitPluginUsage: + type: number + version: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/extend/widgets: + get: + tags: + - admin + summary: /api/admin/extend/widgets + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + templates: + type: array + items: + type: object + properties: + template: + type: string + areas: + type: array + items: + type: object + properties: + name: + type: string + location: + type: string + areas: + type: array + items: + type: object + properties: + name: + type: string + template: + type: string + location: + type: string + data: + type: array + items: + type: object + properties: + widget: + type: string + data: + type: object + properties: + html: + type: string + cid: + type: string + title: + type: string + container: + type: string + groups: + type: array + items: {} + groupsHideFrom: + type: array + items: {} + hide-mobile: + type: string + numTags: + type: string + numUsers: + type: string + text: + type: string + parseAsPost: + type: string + numTopics: + type: string + availableWidgets: + type: array + items: + type: object + properties: + widget: + type: string + name: + type: string + description: + type: string + content: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/extend/rewards: + get: + tags: + - admin + summary: Get rewards settings + responses: + "200": + description: "A JSON object containing rewards and their settings" + content: + application/json: + schema: + allOf: + - type: object + properties: + active: + type: array + items: + type: object + properties: + condition: + type: string + conditional: + type: string + value: + type: number + rid: + type: string + claimable: + type: string + id: + type: string + disabled: + type: boolean + rewards: + type: array + items: + additionalProperties: {} + description: Reward-specific properties + conditions: + type: array + items: + type: object + properties: + name: + type: string + condition: + type: string + conditionals: + type: array + items: + type: object + properties: + name: + type: string + conditional: + type: string + rewards: + type: array + items: + type: object + properties: + rid: + type: string + name: + type: string + inputs: + type: array + items: + type: object + properties: + type: + type: string + name: + type: string + label: + type: string + values: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/advanced/database: + get: + tags: + - admin + summary: Get database information + responses: + "200": + description: "A JSON object with database status information" + content: + application/json: + schema: + properties: {} + additionalProperties: + type: object + description: Each database configured will have an entry here with information about its runtime status + /api/admin/advanced/events: + get: + tags: + - admin + summary: Get event log + parameters: + - in: query + name: type + schema: + type: string + description: Event name to filter by + example: config-change + - in: query + name: start + schema: + type: string + description: Start date to filter by + example: '' + - in: query + name: end + schema: + type: string + description: End date to filter by + example: '' + - in: query + name: perPage + schema: + type: string + description: Limit the number of events returned per page + example: 20 + responses: + "200": + description: "A JSON object containing " + content: + application/json: + schema: + allOf: + - type: object + properties: + events: + type: array + items: + type: object + properties: + type: + type: string + additionalProperties: + description: Each individual event as added by core/plugins can append their own metadata related to the event + - $ref: components/schemas/Pagination.yaml#/Pagination + - type: object + properties: + types: + type: array + items: + type: object + properties: + value: + type: string + name: + type: string + selected: + type: boolean + query: + additionalProperties: + description: An object containing the query string parameters, if any + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/advanced/hooks: + get: + tags: + - admin + summary: Get active plugin hooks + responses: + "200": + description: "A JSON object containing all hooks with active listeners" + content: + application/json: + schema: + allOf: + - type: object + properties: + hooks: + type: array + items: + type: object + properties: + hookName: + type: string + description: The name of the hook (also the name used in code) + methods: + type: array + items: + type: object + properties: + id: + type: string + description: Plugin listening to this hook + priority: + type: number + description: Priority level, lower priorities are executed earlier + method: + type: string + description: Stringified method for examination + index: + type: string + description: Internal counter used for DOM element ids + index: + type: string + description: Internal counter used for DOM element ids + count: + type: number + description: The number of listeners subscribed to this hook + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/advanced/logs: + get: + tags: + - admin + summary: Get server-side log output + responses: + "200": + description: "A JSON object containing the server-side log" + content: + application/json: + schema: + allOf: + - type: object + properties: + data: + type: string + description: Output of the server-side log file + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/advanced/errors: + get: + tags: + - admin + summary: Get server-side errors + responses: + "200": + description: "A JSON object containing server-side errors" + content: + application/json: + schema: + allOf: + - type: object + properties: + not-found: + type: array + items: + type: object + properties: + value: + type: string + description: Path to the requested URL that returned a 404 + score: + type: number + description: The number of times that URL was requested + analytics: + type: object + properties: + not-found: + type: array + description: 404 responses groups by day, from 6 days ago, to present day + items: + type: number + toobusy: + type: array + description: 503 responses groups by day, from 6 days ago, to present day + items: + type: number + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/advanced/errors/export: + get: + tags: + - admin + summary: Export errors (.csv) + responses: + "200": + description: "A CSV file containing server-side errors" + content: + text/csv: + schema: + type: string + format: binary + /api/admin/advanced/cache: + get: + tags: + - admin + summary: /api/admin/advanced/cache + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + postCache: + type: object + properties: + length: + type: number + max: + type: number + nullable: true + itemCount: + type: number + percentFull: + type: number + avgPostSize: + type: number + hits: + type: string + misses: + type: string + hitRatio: + type: string + groupCache: + type: object + properties: + length: + type: number + max: + type: number + itemCount: + type: number + percentFull: + type: number + hits: + type: string + misses: + type: string + hitRatio: + type: string + localCache: + type: object + properties: + length: + type: number + max: + type: number + itemCount: + type: number + percentFull: + type: number + dump: + type: boolean + hits: + type: string + misses: + type: string + hitRatio: + type: string + objectCache: + type: object + properties: + length: + type: number + max: + type: number + itemCount: + type: number + percentFull: + type: number + hits: + type: string + misses: + type: string + hitRatio: + type: string + required: + - postCache + - groupCache + - localCache + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/development/logger: + get: + tags: + - admin + summary: /api/admin/development/logger + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/development/info: + get: + tags: + - admin + summary: Get process/system information + responses: + "200": + description: "A JSON object containing process and system information" + content: + application/json: + schema: + allOf: + - type: object + properties: + info: + type: array + items: + type: object + properties: + process: + type: object + properties: + port: + description: An array containing the port numbers configured to be used by NodeBB processes + oneOf: + - type: array + items: + oneOf: + - type: string + - type: number + - type: string + - type: number + pid: + type: number + description: Process id + title: + type: number + description: Executable + version: + type: number + description: NodeBB version + memoryUsage: + type: object + properties: + rss: + type: number + heapTotal: + type: number + heapUsed: + type: number + external: + type: number + arrayBuffers: + type: number + humanReadable: + type: number + required: + - rss + - heapTotal + - heapUsed + - external + - humanReadable + uptime: + type: number + cpuUsage: + type: object + properties: + user: + type: string + system: + type: string + os: + type: object + properties: + hostname: + type: string + type: + type: string + platform: + type: string + arch: + type: string + release: + type: string + load: + type: string + description: CPU load + git: + type: object + properties: + hash: + type: string + branch: + type: string + stats: + type: object + properties: + onlineGuestCount: + type: number + onlineRegisteredCount: + type: number + socketCount: + type: number + users: + type: object + properties: + categories: + type: number + recent: + type: number + unread: + type: number + topics: + type: number + category: + type: number + topics: + type: array + id: + type: string + infoJSON: + type: string + description: "`info`, but stringified" + host: + type: string + description: Server hostname + port: + description: An array containing the port numbers configured to be used by NodeBB processes + oneOf: + - type: array + items: + oneOf: + - type: string + - type: number + - type: string + - type: number + nodeCount: + type: number + description: The number of NodeBB application processes currently running + timeout: + type: number + ip: + type: string + loggedIn: + type: boolean + relative_path: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/admin/users/csv: + get: + tags: + - admin + summary: Get users export (.csv) + parameters: + - in: header + name: referer + schema: + type: string + required: true + example: /admin/manage/users + responses: + "200": + description: "A CSV file containing all registered users" + content: + text/csv: + schema: + type: string + format: binary + /api/admin/analytics: + get: + tags: + - admin + summary: Get site analytics + parameters: + - in: query + name: units + schema: + type: string + enum: [hours, days] + description: Whether to display dashboard data segmented daily or hourly + example: days + - in: query + name: until + schema: + type: number + description: A UNIX timestamp denoting the end of the analytics reporting period + example: '' + - in: query + name: count + schema: + type: number + description: The number of entries to return (e.g. if `units` is `hourly`, and `count` is `24`, the result set will contain 24 hours' worth of analytics) + example: 20 + responses: + "200": + description: "A JSON object containing analytics data" + content: + application/json: + schema: + type: object + properties: + query: + additionalProperties: + description: The query string passed in + result: + type: object + properties: + uniquevisitors: + type: array + items: + type: number + pageviews: + type: array + items: + type: number + pageviews:registered: + type: array + items: + type: number + pageviews:bot: + type: array + items: + type: number + pageviews:guest: + type: array + items: + type: number + /api/admin/category/uploadpicture: + post: + tags: + - admin + summary: Update category picture (via image upload) + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + cid: + type: number + description: Category identifier whose picture will be set after successful upload + example: 1 + files: + type: array + items: + type: string + format: binary + required: + - cid + - files + responses: + "200": + description: "Image uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded image for use client-side + /api/admin/uploadfavicon: + post: + tags: + - admin + summary: Upload favicon + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "Image uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded image for use client-side + /api/admin/uploadTouchIcon: + post: + tags: + - admin + summary: Upload Touch Icon + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "Image uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded image for use client-side + /api/admin/uploadlogo: + post: + tags: + - admin + summary: Upload site logo + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "Image uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded image for use client-side + /api/admin/uploadOgImage: + post: + tags: + - admin + summary: Upload site-wide Open Graph Image + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "Image uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded image for use client-side + /api/admin/upload/sound: + post: + tags: + - admin + summary: Upload sound file + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "Sound uploaded" + content: + application/json: + schema: + type: object + properties: {} + /api/admin/upload/file: + post: + tags: + - admin + summary: Upload a file + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + folder: + type: string + description: The folder to upload the files to (relative to `public/uploads/`) + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "File uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded file for use client-side + /api/admin/uploadDefaultAvatar: + post: + tags: + - admin + summary: Upload default avatar + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "Image uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded image for use client-side + /api/config: + get: + tags: + - home + summary: /api/config + responses: + "200": + description: "" + content: + application/json: + schema: + type: object + properties: + relative_path: + type: string + upload_url: + type: string + siteTitle: + type: string + browserTitle: + type: string + titleLayout: + type: string + showSiteTitle: + type: boolean + minimumTitleLength: + type: number + maximumTitleLength: + type: number + minimumPostLength: + type: number + maximumPostLength: + type: number + minimumTagsPerTopic: + type: number + maximumTagsPerTopic: + type: number + minimumTagLength: + type: number + maximumTagLength: + type: number + useOutgoingLinksPage: + type: boolean + allowGuestHandles: + type: boolean + allowFileUploads: + type: boolean + allowTopicsThumbnail: + type: boolean + usePagination: + type: boolean + disableChat: + type: boolean + disableChatMessageEditing: + type: boolean + maximumChatMessageLength: + type: number + socketioTransports: + type: array + items: + type: string + socketioOrigins: + type: string + websocketAddress: + type: string + maxReconnectionAttempts: + type: number + reconnectionDelay: + type: number + topicsPerPage: + type: number + postsPerPage: + type: number + maximumFileSize: + type: number + theme:id: + type: string + theme:src: + type: string + defaultLang: + type: string + userLang: + type: string + loggedIn: + type: boolean + uid: + type: number + description: A user identifier + cache-buster: + type: string + requireEmailConfirmation: + type: boolean + topicPostSort: + type: string + categoryTopicSort: + type: string + csrf_token: + type: string + searchEnabled: + type: boolean + bootswatchSkin: + type: string + enablePostHistory: + type: boolean + notificationAlertTimeout: + type: number + timeagoCutoff: + type: number + timeagoCodes: + type: array + items: + type: string + cookies: + type: object + properties: + enabled: + type: boolean + message: + type: string + dismiss: + type: string + link: + type: string + link_url: + type: string + acpLang: + type: string + openOutgoingLinksInNewTab: + type: boolean + topicSearchEnabled: + type: boolean + hideSubCategories: + type: boolean + hideCategoryLastPost: + type: boolean + enableQuickReply: + type: boolean + /api/me: + get: + tags: + - shorthand + summary: /api/me + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/UserObject.yaml#/UserObjectFull + "/api/user/uid/{uid}": + get: + tags: + - users + summary: /api/user/uid/{uid} + parameters: + - name: uid + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/UserObject.yaml#/UserObject + "/api/user/username/{username}": + get: + tags: + - users + summary: /api/user/username/{username} + parameters: + - name: username + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/UserObject.yaml#/UserObject + "/api/user/email/{email}": + get: + tags: + - users + summary: /api/user/email/{email} + parameters: + - name: email + in: path + required: true + schema: + type: string + example: 'test@example.org' + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/UserObject.yaml#/UserObject + "/api/user/uid/{userslug}/export/posts": + get: + tags: + - users + summary: Export a user's posts (.csv) + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "A CSV file containing a user's posts" + content: + text/csv: + schema: + type: string + format: binary + "/api/user/uid/{userslug}/export/uploads": + get: + tags: + - users + summary: /api/user/uid/{userslug}/export/uploads + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: Successful export of user uploads + content: + application/zip: + schema: + type: string + format: binary + "/api/user/uid/{userslug}/export/profile": + get: + tags: + - users + summary: /api/user/uid/{userslug}/export/profile + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "A CSV file containing the user profile" + content: + text/csv: + schema: + type: string + format: binary + "/api/post/pid/{id}": + get: + tags: + - shorthand + summary: Get post data + parameters: + - name: id + in: path + required: true + schema: + type: number + example: 1 + responses: + "200": + description: "A JSON object containing post data" + content: + application/json: + schema: + type: object + properties: + uid: + type: number + tid: + type: number + timestamp: + type: number + content: + type: string + pid: + type: number + downvotes: + type: number + upvotes: + type: number + deleted: + type: number + deleterUid: + type: number + edited: + type: number + votes: + type: number + timestampISO: + type: string + editedISO: + type: string + upvoted: + type: boolean + downvoted: + type: boolean + "/api/topic/tid/{id}": + get: + tags: + - shorthand + summary: Get topic data + parameters: + - name: id + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "A JSON object containing topic data" + content: + application/json: + schema: + type: object + properties: + tid: + type: number + uid: + type: number + cid: + type: number + mainPid: + type: number + teaserPid: + type: number + nullable: true + title: + type: string + slug: + type: string + timestamp: + type: number + lastposttime: + type: number + postcount: + type: number + viewcount: + type: number + deleted: + type: number + locked: + type: number + pinned: + type: number + upvotes: + type: number + downvotes: + type: number + deleterUid: + type: number + titleRaw: + type: string + timestampISO: + type: string + lastposttimeISO: + type: string + votes: + type: number + "/api/category/cid/{id}": + get: + tags: + - shorthand + summary: Get category data + parameters: + - name: id + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "A JSON object containing topic data" + content: + application/json: + schema: + type: object + properties: + cid: + type: number + name: + type: number + description: + type: string + descriptionParsed: + type: string + icon: + type: string + bgColor: + type: string + color: + type: string + slug: + type: string + parentCid: + type: number + topic_count: + type: number + post_count: + type: number + disabled: + type: number + order: + type: number + link: + type: string + numRecentReplies: + type: number + class: + type: string + imageClass: + type: string + isSection: + type: number + totalPostCount: + type: number + totalTopicCount: + type: number + /api/categories: + get: + tags: + - categories + summary: Get a list of categories + description: > + This route retrieve the list of categories currently available to the + accessing user. It doesn't necessarily mean that the user can *enter* + the category, as that is a separate privilege. Specifically, this route + will return all categories that grant the calling user the "Find + Category" privilege. + + + Subcategories are also returned, nested under a category's `children` property. + responses: + "200": + description: A list of category objectscurrently available to the accessing user + content: + application/json: + schema: + allOf: + - type: object + properties: + title: + description: The page title + type: string + categories: + description: A collection of category data objects + type: array + items: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + children: + type: array + items: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + children: + type: array + items: + $ref: components/schemas/CategoryObject.yaml#/CategoryObject + parent: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + posts: + type: array + items: + type: object + properties: + pid: + type: number + timestamp: + type: number + content: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to + users without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction + with `icon:text` for the user's + auto-generated icon + example: "#f44336" + index: + type: number + cid: + type: number + description: A category identifier + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + topic: + type: object + properties: + slug: + type: string + title: + type: string + imageClass: + type: string + timesClicked: + type: number + posts: + type: array + items: + type: object + properties: + pid: + type: number + timestamp: + type: number + content: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users + without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's + auto-generated icon + example: "#f44336" + index: + type: number + cid: + type: number + description: A category identifier + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + topic: + type: object + properties: + slug: + type: string + title: + type: string + teaser: + type: object + properties: + url: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + pid: + type: number + topic: + type: object + properties: + slug: + type: string + title: + type: string + imageClass: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/categories/{cid}/moderators": + get: + tags: + - categories + summary: Get mods for a category + description: > + This route returns an array of uids that correspond to the moderators + for the category in question. + parameters: + - name: cid + description: The category identifier for the category you wish to look up + in: path + required: true + schema: + type: number + example: 1 + responses: + "200": + description: An array of moderators for the requested category + content: + application/json: + schema: + type: object + properties: + moderators: + type: array + example: + moderators: + - 1 + - 2 + - 3 + "/api/recent/posts/{term?}": + get: + tags: + - topics + summary: /api/recent/posts/{term?} + parameters: + - name: term? + in: path + required: true + schema: + type: string + example: day + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/PostsObject.yaml#/PostsObject + /api/unread/total: + get: + tags: + - topics + summary: Get number of unread topics + responses: + "200": + description: "Success" + content: + text/plain: + schema: + type: number + "/api/topic/teaser/{topic_id}": + get: + tags: + - topics + summary: Get a topic's teaser post + parameters: + - name: topic_id + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "A JSON object containing the teaser post for a topic" + content: + application/json: + schema: + $ref: components/schemas/PostsObject.yaml#/PostsObject + "/api/topic/pagination/{topic_id}": + get: + tags: + - topics + summary: /api/topic/pagination/{topic_id} + parameters: + - name: topic_id + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: components/schemas/Pagination.yaml#/Pagination + /api/post/upload: + post: + tags: + - posts + summary: /api/post/upload + responses: + "200": + description: "" + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + text/plain: + schema: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + "403": + description: "" + content: + application/json: + schema: + type: string + example: Forbidden + text/plain: + schema: + type: string + example: Forbidden + "500": + description: "" + content: + application/json: + schema: + type: object + properties: + path: + type: string + error: + type: string + text/plain: + schema: + type: object + properties: + path: + type: string + error: + type: string + /api/topic/thumb/upload: + post: + tags: + - topics + summary: Upload topic thumb + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + responses: + "200": + description: "Image uploaded" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The filename + url: + type: string + description: URL of the uploaded image for use client-side + path: + type: string + description: Path to the file in the local file system + /api/login: + get: + tags: + - authentication + summary: /api/login + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + loginFormEntry: + type: array + items: + type: object + properties: + label: + type: string + description: A label for the added block + html: + type: string + description: HTML to render on the login page + styleName: + type: string + description: Custom identifier (value is added to `input[id]` and `label[for]`) + alternate_logins: + type: boolean + authentication: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + callbackURL: + type: string + icon: + type: string + scope: + type: string + prompt: + type: string + allowRegistration: + type: boolean + allowLoginWith: + type: string + title: + type: string + allowPasswordReset: + type: boolean + allowLocalLogin: + type: boolean + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/register: + get: + tags: + - authentication + summary: /api/register + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + register_window:spansize: + type: string + alternate_logins: + type: boolean + authentication: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + callbackURL: + type: string + icon: + type: string + scope: + type: string + prompt: + type: string + minimumUsernameLength: + type: number + maximumUsernameLength: + type: number + minimumPasswordLength: + type: number + minimumPasswordStrength: + type: number + regFormEntry: + type: array + items: + type: object + properties: + label: + type: string + html: + type: string + styleName: + type: string + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + # /api/register/complete: + # get: + # tags: + # - authentication + # summary: /api/register/complete + # responses: + # "200": + # description: "" + # content: + # application/json: + # schema: + # allOf: + # - type: object + # properties: + # title: + # type: string + # errors: + # type: array + # items: {} + # sections: + # type: array + # items: + # type: string + # - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/search: + get: + tags: + - search + summary: /api/search + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + matchCount: + type: number + pageCount: + type: number + time: + type: string + multiplePages: + type: boolean + search_query: + type: string + term: + type: string + categories: + type: array + items: + type: object + properties: + value: + oneOf: + - type: string + - type: number + text: + type: string + categoriesCount: + type: number + expandSearch: + type: boolean + showAsPosts: + type: boolean + showAsTopics: + type: boolean + title: + type: string + searchDefaultSortBy: + type: string + required: + - posts + - matchCount + - pageCount + - time + - multiplePages + - search_query + - categories + - categoriesCount + - expandSearch + - showAsPosts + - showAsTopics + - title + - searchDefaultSortBy + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/reset": + get: + tags: + - authentication + summary: Get user password reset (step 1) + responses: + "200": + description: "A JSON object containing the 1st step of the user password reset flow" + content: + application/json: + schema: + allOf: + - type: object + properties: + code: + type: string + nullable: true + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/reset/{code}": + get: + tags: + - authentication + summary: Get user password reset (step 2) + parameters: + - name: code + in: path + required: true + schema: + type: string + example: testCode + responses: + "200": + description: "A JSON object containing the 2nd step of the user password reset flow" + content: + application/json: + schema: + allOf: + - type: object + properties: + valid: + type: boolean + code: + type: string + minimumPasswordLength: + type: number + minimumPasswordStrength: + type: number + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/email/unsubscribe/{token}": + # TODO: Need GET route here as well + post: + tags: + - emails + summary: /api/email/unsubscribe/{token} + parameters: + - name: token + in: path + required: true + schema: + type: string + example: testToken + responses: + "200": + description: "Successfully unsubscribed" + "500": + description: "Server-side error (likely token verification failure)" + "/api/topic/{topic_id}/{slug}/{post_index}": + get: + tags: + - topics + summary: /api/topic/{topic_id}/{slug}/{post_index} + parameters: + - name: topic_id + in: path + required: true + schema: + type: string + example: 1 + - name: slug + in: path + required: true + schema: + type: string + example: test-topic + - name: post_index + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + tid: + type: number + description: A topic identifier + uid: + type: number + description: A user identifier + cid: + type: number + description: A category identifier + title: + type: string + slug: + type: string + timestamp: + type: number + lastposttime: + type: number + postcount: + type: number + viewcount: + type: number + mainPid: + type: number + description: The post id of the first post in this topic (also called the + "original post") + teaserPid: + type: number + nullable: true + upvotes: + type: number + downvotes: + type: number + deleted: + type: number + locked: + type: number + pinned: + type: number + description: Whether or not this particular topic is pinned to the top of the + category + deleterUid: + type: number + titleRaw: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + lastposttimeISO: + type: string + votes: + type: number + tags: + type: array + items: + type: object + properties: + value: + type: string + valueEscaped: + type: string + color: + type: string + bgColor: + type: string + score: + type: number + posts: + type: array + items: + type: object + properties: + pid: + type: number + uid: + type: number + description: A user identifier + tid: + type: number + description: A topic identifier + content: + type: string + timestamp: + type: number + votes: + type: number + deleted: + type: number + upvotes: + type: number + downvotes: + type: number + deleterUid: + type: number + edited: + type: number + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + editedISO: + type: string + index: + type: number + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + reputation: + type: number + postcount: + type: number + topiccount: + type: number + picture: + type: string + nullable: true + signature: + type: string + banned: + type: number + banned:expire: + type: number + status: + type: string + lastonline: + type: number + groupTitle: + nullable: true + type: string + groupTitleArray: + type: array + items: + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + lastonlineISO: + type: string + banned_until: + type: number + banned_until_readable: + type: string + selectedGroups: + type: array + items: + type: object + properties: + name: + type: string + slug: + type: string + labelColor: + type: string + textColor: + type: string + icon: + type: string + userTitle: + type: string + custom_profile_info: + type: array + items: + type: object + properties: + content: + type: string + description: HTML that is injected into `topic.tpl` of themes that support custom profile info + editor: + nullable: true + bookmarked: + type: boolean + upvoted: + type: boolean + downvoted: + type: boolean + replies: + type: object + properties: + hasMore: + type: boolean + users: + type: array + items: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + administrator: + type: boolean + text: + type: string + count: + type: number + selfPost: + type: boolean + display_edit_tools: + type: boolean + display_delete_tools: + type: boolean + display_moderator_tools: + type: boolean + display_move_tools: + type: boolean + display_post_menu: + type: boolean + category: + $ref: components/schemas/CategoryObject.yaml#/CategoryObject + tagWhitelist: + type: array + items: + type: string + thread_tools: + type: array + items: + type: object + properties: + class: + type: string + title: + type: string + icon: + type: string + isFollowing: + type: boolean + isNotFollowing: + type: boolean + isIgnoring: + type: boolean + bookmark: + nullable: true + postSharing: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + class: + type: string + activated: + type: boolean + deleter: + nullable: true + merger: + nullable: true + related: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + unreplied: + type: boolean + icons: + type: array + items: + type: string + description: HTML that is rendered by the theme + privileges: + type: object + properties: + topics:reply: + type: boolean + topics:read: + type: boolean + topics:tag: + type: boolean + topics:delete: + type: boolean + posts:edit: + type: boolean + posts:history: + type: boolean + posts:delete: + type: boolean + posts:view_deleted: + type: boolean + read: + type: boolean + purge: + type: boolean + view_thread_tools: + type: boolean + editable: + type: boolean + deletable: + type: boolean + view_deleted: + type: boolean + isAdminOrMod: + type: boolean + disabled: + type: number + tid: + type: string + uid: + type: number + description: A user identifier + topicStaleDays: + type: number + reputation:disabled: + type: number + downvote:disabled: + type: number + feeds:disableRSS: + type: number + bookmarkThreshold: + type: number + necroThreshold: + type: number + postEditDuration: + type: number + postDeleteDuration: + type: number + scrollToMyPost: + type: boolean + allowMultipleBadges: + type: boolean + privateUploads: + type: boolean + rssFeedUrl: + type: string + postIndex: + type: number + loggedInUser: + $ref: components/schemas/UserObject.yaml#/UserObject + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/topic/{topic_id}/{slug}": + get: + tags: + - topics + summary: /api/topic/{topic_id}/{slug} + parameters: + - name: topic_id + in: path + required: true + schema: + type: string + example: 1 + - name: slug + in: path + required: true + schema: + type: string + example: '' + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Copy response from the route w/o post_index" + "/api/post/{pid}": + get: + tags: + - shorthand + summary: Access a specific post + description: This route comes in handy when all you have is the `pid`, and you want to redirect users to the canonical URL for the topic, with the appropriate topic slug and post index. + parameters: + - name: pid + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "Canonical URL of topic" + content: + text/plain: + schema: + type: string + /api/flags: + get: + tags: + - flags + summary: /api/flags + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + flags: + type: array + items: + type: object + properties: + state: + type: string + flagId: + type: number + type: + type: string + targetId: + oneOf: + - type: string + - type: number + description: + type: string + uid: + type: number + description: A user identifier + datetime: + type: number + reporter: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + picture: + nullable: true + type: string + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + labelClass: + type: string + target_readable: + type: string + datetimeISO: + type: string + assignee: + type: string + nullable: true + analytics: + type: array + items: + type: number + categories: + type: object + properties: {} + additionalProperties: + type: string + description: All categories will be listed here, with the `cid` as the key, and the category name as the value + hasFilter: + type: boolean + filters: + type: object + properties: + page: + type: number + perPage: + type: number + title: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/flags/{flagId}": + get: + tags: + - flags + summary: /api/flags/{flagId} + parameters: + - name: flagId + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + state: + type: string + flagId: + type: number + type: + type: string + targetId: + type: number + description: + type: string + uid: + type: number + description: A user identifier + datetime: + type: number + datetimeISO: + type: string + target_readable: + type: string + target: + type: object + properties: {} + additionalProperties: + description: Properties change depending on the target type (user, post, etc.) + assignee: + type: number + nullable: true + filters: + type: object + properties: + page: + type: number + perPage: + type: number + history: + type: array + items: + type: object + properties: + uid: + type: number + description: A user identifier + fields: + type: object + properties: + state: + type: string + datetime: + type: number + datetimeISO: + type: string + user: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + notes: + type: array + items: + type: object + properties: + uid: + type: number + content: + type: string + datetime: + type: number + datetimeISO: + type: string + user: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + reporter: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + reputation: + type: number + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without an + avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with `icon:text` for + the user's auto-generated icon + example: "#f44336" + type_path: + type: string + assignees: + type: array + items: + $ref: components/schemas/UserObject.yaml#/UserObject + type_bool: + type: object + properties: + post: + type: boolean + user: + type: boolean + empty: + type: boolean + title: + type: string + categories: + type: object + additionalProperties: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/post-queue: + get: + tags: + - admin + summary: /api/post-queue + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + title: + type: string + posts: + type: array + items: + allOf: + - type: object + properties: + id: + type: string + uid: + type: number + description: A user identifier + type: + type: string + data: + type: object + properties: + title: + type: string + content: + type: string + thumb: + type: string + cid: + oneOf: + - type: number + - type: string + tags: + type: array + items: {} + uid: + type: number + description: A user identifier + req: + type: object + properties: + uid: + type: number + description: A user identifier + ip: + type: string + host: + type: string + protocol: + type: string + secure: + type: boolean + url: + type: string + path: + type: string + headers: + type: object + properties: + x-real-ip: + type: string + x-forwarded-for: + type: string + x-forwarded-proto: + type: string + host: + type: string + x-nginx-proxy: + type: string + connection: + type: string + accept: + type: string + user-agent: + type: string + sec-fetch-site: + type: string + sec-fetch-mode: + type: string + referer: + type: string + accept-encoding: + type: string + accept-language: + type: string + cookie: + type: string + timestamp: + type: number + fromQueue: + type: boolean + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + rawContent: + type: string + tid: + type: number + description: A topic identifier + toPid: + nullable: true + user: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + topic: + type: object + properties: + cid: + type: number + title: + type: string + titleRaw: + type: string + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/ip-blacklist: + get: + tags: + - admin + summary: /api/ip-blacklist + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Copy response from corresponding admin route" + /api/registration-queue: + get: + tags: + - admin + summary: /api/registration-queue + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Copy response from corresponding admin route" + "/api/tags/{tag}": + get: + tags: + - tags + summary: /api/tags/{tag} + description: Returns a list of topics that are tagged with {tag} + parameters: + - name: tag + description: The tag used to retrieve the topics + in: path + required: true + schema: + type: string + example: test + - name: page + description: Page number used in pagination + in: query + required: false + schema: + type: number + example: '' + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + topics: + type: array + description: An array of topics that are all tagged with {tag} + items: + type: object + properties: + tid: + type: number + description: A topic identifier + uid: + type: number + description: A user identifier + cid: + type: number + description: A category identifier + mainPid: + type: number + description: The post id of the first post in this topic (also called the + "original post") + title: + type: string + slug: + type: string + timestamp: + type: number + lastposttime: + type: number + postcount: + type: number + viewcount: + type: number + teaserPid: + oneOf: + - type: number + - type: string + deleted: + type: number + locked: + type: number + pinned: + type: number + description: Whether or not this particular topic is pinned to the top of the + category + upvotes: + type: number + downvotes: + type: number + titleRaw: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + lastposttimeISO: + type: string + votes: + type: number + category: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + slug: + type: string + icon: + type: string + image: + nullable: true + imageClass: + nullable: true + type: string + bgColor: + type: string + color: + type: string + disabled: + type: number + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + reputation: + type: number + postcount: + type: number + picture: + nullable: true + type: string + signature: + nullable: true + type: string + banned: + type: number + status: + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + banned_until_readable: + type: string + fullname: + type: string + teaser: + type: object + properties: + pid: + type: number + uid: + type: number + description: A user identifier + timestamp: + type: number + tid: + type: number + description: A topic identifier + content: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users + without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's + auto-generated icon + example: "#f44336" + index: + type: number + tags: + type: array + items: + type: object + properties: + value: + type: string + valueEscaped: + type: string + color: + type: string + bgColor: + type: string + score: + type: number + isOwner: + type: boolean + ignored: + type: boolean + unread: + type: boolean + bookmark: + nullable: true + unreplied: + type: boolean + icons: + type: array + items: {} + index: + type: number + thumb: + type: string + isQuestion: + nullable: true + type: number + isSolved: + type: number + tag: + type: string + title: + type: string + categories: + type: array + items: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + selected: + type: boolean + imageClass: + type: string + rssFeedUrl: + type: string + required: + - topics + - tag + - title + - categories + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/tags: + get: + tags: + - tags + summary: /api/tags + description: Returns a list of tags sorted by the most topics + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + tags: + type: array + description: An array of tags sorted by the most topics + items: + type: object + properties: + value: + type: string + description: The raw tag + score: + type: number + description: Number of topics tagged by this tag + valueEscaped: + type: string + description: This is the escaped tag value, equal to validator.escape(value) + color: + type: string + bgColor: + type: string + displayTagSearch: + type: boolean + nextStart: + type: number + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/popular: + get: + tags: + - topics + summary: Get Popular Topics + description: Returns a list of topics sorted by most replies. In an event of a + tie breaker, the topic with the most views. Can be filtered by All Time, + Day, Week, or Month. + responses: + "200": + description: An array of topic objects sorted by most replies and views. + content: + application/json: + schema: + allOf: + - type: object + properties: + nextStart: + type: number + topicCount: + type: number + topics: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + tids: + type: array + items: + type: number + canPost: + type: boolean + categories: + type: array + items: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + selected: + type: boolean + imageClass: + type: string + allCategoriesUrl: + type: string + selectedCategory: + type: object + properties: + icon: + type: string + name: + type: string + bgColor: + type: string + nullable: true + selectedCids: + type: array + items: + type: number + feeds:disableRSS: + type: number + rssFeedUrl: + type: string + title: + type: string + filters: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + selectedFilter: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + terms: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + term: + type: string + selectedTerm: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + term: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/recent: + get: + tags: + - topics + summary: Recent Topics + description: Returns a list of topics sorted by timestamp. + responses: + "200": + description: An array of topic objects sorted by timestamp. + content: + application/json: + schema: + allOf: + - type: object + properties: + nextStart: + type: number + topicCount: + type: number + topics: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + tids: + type: array + items: + type: number + canPost: + type: boolean + categories: + type: array + items: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + selected: + type: boolean + imageClass: + type: string + allCategoriesUrl: + type: string + selectedCategory: + type: object + properties: + icon: + type: string + name: + type: string + bgColor: + type: string + nullable: true + selectedCids: + type: array + items: + type: number + feeds:disableRSS: + type: number + rssFeedUrl: + type: string + title: + type: string + filters: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + selectedFilter: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + terms: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + term: + type: string + selectedTerm: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + term: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/top: + get: + tags: + - topics + summary: Top Topics + description: Returns a list of topics sorted by most upvotes. + responses: + "200": + description: An array of topic objects sorted by most upvotes. + content: + application/json: + schema: + allOf: + - type: object + properties: + nextStart: + type: number + topicCount: + type: number + topics: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + tids: + type: array + items: + type: number + canPost: + type: boolean + categories: + type: array + items: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + selected: + type: boolean + imageClass: + type: string + allCategoriesUrl: + type: string + selectedCategory: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + selected: + type: boolean + nullable: true + selectedCids: + type: array + items: + type: number + feeds:disableRSS: + type: number + rssFeedUrl: + type: string + title: + type: string + filters: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + selectedFilter: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + terms: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + term: + type: string + selectedTerm: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + term: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/unread: + get: + tags: + - topics + summary: Unread Topics + description: Returns a list of the current user's unread topics, sorted by the + last post's timestamp. + responses: + "200": + description: An array of unread topic objects sorted by the last post's timestamp. + content: + application/json: + schema: + allOf: + - type: object + properties: + showSelect: + type: boolean + nextStart: + type: number + topics: + type: array + items: + type: object + properties: + tid: + type: number + description: A topic identifier + uid: + type: number + description: A user identifier + cid: + type: number + description: A category identifier + mainPid: + type: number + description: The post id of the first post in this topic (also called the + "original post") + title: + type: string + slug: + type: string + timestamp: + type: number + lastposttime: + type: number + postcount: + type: number + viewcount: + type: number + teaserPid: + type: number + upvotes: + type: number + downvotes: + type: number + deleted: + type: number + locked: + type: number + pinned: + type: number + description: Whether or not this particular topic is pinned to the top of the + category + titleRaw: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + lastposttimeISO: + type: string + votes: + type: number + category: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + slug: + type: string + icon: + type: string + image: + nullable: true + imageClass: + nullable: true + type: string + bgColor: + type: string + color: + type: string + disabled: + type: number + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + fullname: + type: string + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + reputation: + type: number + postcount: + type: number + picture: + nullable: true + type: string + signature: + nullable: true + type: string + banned: + type: number + status: + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + banned_until_readable: + type: string + teaser: + type: object + properties: + pid: + type: number + uid: + type: number + description: A user identifier + timestamp: + type: number + tid: + type: number + description: A topic identifier + content: + type: string + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users + without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's + auto-generated icon + example: "#f44336" + index: + type: number + tags: + type: array + items: + type: object + properties: + value: + type: string + valueEscaped: + type: string + color: + type: string + bgColor: + type: string + score: + type: number + isOwner: + type: boolean + ignored: + type: boolean + unread: + type: boolean + bookmark: + nullable: true + unreplied: + type: boolean + icons: + type: array + items: + type: string + index: + type: number + isQuestion: + nullable: true + topicCount: + type: number + title: + type: string + pageCount: + type: number + categories: + type: array + items: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + selected: + type: boolean + imageClass: + type: string + allCategoriesUrl: + type: string + selectedCids: + type: array + items: + type: number + filters: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + selectedFilter: + type: object + properties: + name: + type: string + url: + type: string + selected: + type: boolean + filter: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/category/{category_id}/{slug}/{topic_index}": + get: + tags: + - categories + summary: /api/category/{category_id}/{slug}/{topic_index} + parameters: + - name: category_id + in: path + required: true + schema: + type: string + example: 1 + - name: slug + in: path + required: true + schema: + type: string + example: test + - name: topic_index + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/CategoryObject.yaml#/CategoryObject + - type: object + properties: + tagWhitelist: + type: array + items: + type: string + unread-class: + type: string + children: + type: array + items: + $ref: components/schemas/CategoryObject.yaml#/CategoryObject + topics: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + nextStart: + type: number + isWatched: + type: boolean + isNotWatched: + type: boolean + isIgnored: + type: boolean + title: + type: string + privileges: + type: object + properties: + topics:create: + type: boolean + topics:read: + type: boolean + topics:tag: + type: boolean + read: + type: boolean + cid: + type: string + uid: + type: number + description: A user identifier + editable: + type: boolean + view_deleted: + type: boolean + isAdminOrMod: + type: boolean + showSelect: + type: boolean + rssFeedUrl: + type: string + feeds:disableRSS: + type: number + reputation:disabled: + type: number + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/category/{category_id}/{slug?}": + get: + tags: + - categories + summary: /api/category/{category_id}/{slug} + parameters: + - name: category_id + in: path + required: true + schema: + type: string + example: 1 + - name: slug + in: path + required: true + schema: + type: string + example: test + responses: + "418": + description: "TODO: A proper response needs to be added. It is not really a teapot | Copy response from indexed variant" + /api/me/*: + get: + tags: + - shorthand + summary: Access your own profile's pages + description: >- + This shorthand is useful if you want to link to pages in your own account profile, but do not want (or have) the `userslug`. It is also especially useful as a + means to instruct users on how to do things, as you can easily redirect them to their own profile pages. + responses: + "200": + description: "Canonical URL to your requested profile page" + "/api/uid/{uid}/*": + get: + tags: + - shorthand + summary: Access a user's profile pages + description: >- + This particular shorthand is useful if you are looking to redirect to a user's profile (or other associated pages), but do not know or want to retrieve their userslug, + which is part of the canonical url. + + For example, to go to `uid` 15's list of topics made, you can navigate to `/api/uid/15/topics`, which will send you to the appropriate canonical URL for that user's topics. + parameters: + - name: uid + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "Canonical URL of user profile page" + "/api/user/{userslug}": + get: + tags: + - users + summary: Get user profile + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + latestPosts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + bestPosts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + hasPrivateChat: + type: number + title: + type: string + allowCoverPicture: + type: boolean + selectedGroup: + type: array + items: + type: object + properties: + name: + type: string + slug: + type: string + createtime: + type: number + userTitle: + type: string + description: + type: string + memberCount: + type: number + deleted: + type: string + hidden: + type: number + system: + type: number + private: + type: number + ownerUid: + type: number + icon: + type: string + labelColor: + type: string + cover:url: + type: string + cover:position: + type: string + userTitleEnabled: + type: number + disableJoinRequests: + type: number + disableLeave: + type: number + nameEncoded: + type: string + displayName: + type: string + textColor: + type: string + createtimeISO: + type: string + cover:thumb:url: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/following": + get: + tags: + - users + summary: Get followed users + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + - name: page + in: query + schema: + type: number + example: '' + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + title: + type: string + users: + type: array + items: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + status: + type: string + postcount: + type: number + reputation: + type: number + email:confirmed: + type: number + description: Whether the user has confirmed their email address or not + lastonline: + type: number + flags: + nullable: true + banned: + type: number + banned:expire: + type: number + joindate: + type: number + description: A UNIX timestamp representing the moment the user's account was + created + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without an + avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with `icon:text` + for the user's auto-generated icon + example: "#f44336" + joindateISO: + type: string + lastonlineISO: + type: string + banned_until: + type: number + banned_until_readable: + type: string + administrator: + type: boolean + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/followers": + get: + tags: + - users + summary: Get followers + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + - name: page + in: query + schema: + type: number + example: '' + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + title: + type: string + users: + type: array + items: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + status: + type: string + postcount: + type: number + reputation: + type: number + email:confirmed: + type: number + description: Whether the user has confirmed their email address or not + lastonline: + type: number + flags: + nullable: true + banned: + type: number + banned:expire: + type: number + joindate: + type: number + description: A UNIX timestamp representing the moment the user's account was + created + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without an + avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with `icon:text` + for the user's auto-generated icon + example: "#f44336" + joindateISO: + type: string + lastonlineISO: + type: string + banned_until: + type: number + banned_until_readable: + type: string + administrator: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/categories": + get: + tags: + - users + summary: /api/user/{userslug}/categories + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + categories: + type: array + items: + type: object + properties: + cid: + type: number + description: A category identifier + name: + type: string + level: + type: string + icon: + type: string + parentCid: + type: number + description: The category identifier for the category that is the immediate + ancestor of the current category + color: + type: string + bgColor: + type: string + descriptionParsed: + type: string + depth: + type: number + slug: + type: string + isIgnored: + type: boolean + isWatched: + type: boolean + isNotWatched: + type: boolean + imageClass: + type: string + title: + type: string + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/posts": + get: + tags: + - users + summary: /api/user/{userslug}/posts + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + nextStart: + type: number + noItemsFoundKey: + type: string + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/topics": + get: + tags: + - users + summary: /api/user/{userslug}/topics + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + topics: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + nextStart: + type: number + noItemsFoundKey: + type: string + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/best": + get: + tags: + - users + summary: /api/user/{userslug}/best + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + nextStart: + type: number + noItemsFoundKey: + type: string + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/groups": + get: + tags: + - users + summary: /api/user/{userslug}/groups + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + title: + type: string + template: + type: object + properties: + name: + type: string + account/groups: + type: boolean + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/bookmarks": + get: + tags: + - users + summary: /api/user/{userslug}/bookmarks + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + nextStart: + type: number + noItemsFoundKey: + type: string + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/watched": + get: + tags: + - users + summary: /api/user/{userslug}/watched + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObject + - type: object + properties: + aboutmeParsed: + type: string + age: + type: number + emailClass: + type: string + ips: + type: array + items: + type: string + isBlocked: + type: boolean + blocksCount: + type: number + yourid: + type: number + theirid: + type: number + isTargetAdmin: + type: boolean + isAdmin: + type: boolean + isGlobalModerator: + type: boolean + isModerator: + type: boolean + isAdminOrGlobalModerator: + type: boolean + isAdminOrGlobalModeratorOrModerator: + type: boolean + isSelfOrAdminOrGlobalModerator: + type: boolean + canEdit: + type: boolean + canBan: + type: boolean + canChangePassword: + type: boolean + isSelf: + type: boolean + isFollowing: + type: boolean + hasPrivateChat: + type: number + showHidden: + type: boolean + groups: + type: array + items: + type: object + properties: + name: + type: string + slug: + type: string + createtime: + type: number + userTitle: + type: string + description: + type: string + memberCount: + type: number + deleted: + oneOf: + - type: string + - type: number + hidden: + type: number + system: + type: number + private: + type: number + ownerUid: + type: number + icon: + type: string + labelColor: + type: string + userTitleEnabled: + type: number + disableJoinRequests: + type: number + disableLeave: + type: number + nameEncoded: + type: string + displayName: + type: string + textColor: + type: string + createtimeISO: + type: string + cover:thumb:url: + type: string + cover:url: + type: string + cover:position: + type: string + disableSignatures: + type: boolean + reputation:disabled: + type: boolean + downvote:disabled: + type: boolean + profile_links: + type: array + items: + type: object + properties: + id: + type: string + route: + type: string + name: + type: string + visibility: + type: object + properties: + self: + type: boolean + other: + type: boolean + moderator: + type: boolean + globalMod: + type: boolean + admin: + type: boolean + canViewInfo: + type: boolean + public: + type: boolean + icon: + type: string + sso: + type: array + items: + type: object + properties: + associated: + type: boolean + url: + type: string + deauthUrl: + type: string + name: + type: string + icon: + type: string + websiteLink: + type: string + websiteName: + type: string + moderationNote: + type: string + username:disableEdit: + type: boolean + email:disableEdit: + type: boolean + topics: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + nextStart: + type: number + noItemsFoundKey: + type: string + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/ignored": + get: + tags: + - users + summary: /api/user/{userslug}/ignored + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + topics: + type: array + items: + $ref: components/schemas/TopicObject.yaml#/TopicObject + nextStart: + type: number + noItemsFoundKey: + type: string + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/upvoted": + get: + tags: + - users + summary: /api/user/{userslug}/upvoted + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + nextStart: + type: number + noItemsFoundKey: + type: string + description: Translation key for message notifying user that there were no posts found + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + required: + - posts + - nextStart + - noItemsFoundKey + - title + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/downvoted": + get: + tags: + - users + summary: Get user's downvotes + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + nextStart: + type: number + noItemsFoundKey: + type: string + description: Translation key for message notifying user that there were no posts found + title: + type: string + showSort: + type: boolean + sortOptions: + type: array + items: + type: object + properties: + url: + type: string + name: + type: string + selected: + type: boolean + required: + - posts + - nextStart + - noItemsFoundKey + - title + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/edit": + get: + tags: + - users + summary: /api/user/{userslug}/edit + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + maximumSignatureLength: + type: number + maximumAboutMeLength: + type: number + maximumProfileImageSize: + type: number + allowProfilePicture: + type: boolean + allowCoverPicture: + type: boolean + allowProfileImageUploads: + type: number + allowedProfileImageExtensios: + type: string + allowMultipleBadges: + type: boolean + allowAccountDelete: + type: boolean + allowWebsite: + type: boolean + allowAboutMe: + type: boolean + allowSignature: + type: boolean + profileImageDimension: + type: number + defaultAvatar: + type: string + groupSelectSize: + type: number + title: + type: string + editButtons: + type: array + items: + type: object + properties: + link: + type: string + description: A relative path to the page linked to + text: + type: string + description: Button label + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/edit/username": + get: + tags: + - users + summary: /api/user/{userslug}/edit/username + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + hasPassword: + type: boolean + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/edit/email": + get: + tags: + - users + summary: /api/user/{userslug}/edit/email + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + hasPassword: + type: boolean + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/edit/password": + get: + tags: + - users + summary: /api/user/{userslug}/edit/password + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + hasPassword: + type: boolean + minimumPasswordLength: + type: number + minimumPasswordStrength: + type: number + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/info": + get: + tags: + - users + summary: /api/user/{userslug}/info + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + history: + type: object + properties: + flags: + type: array + items: + type: object + properties: + pid: + type: number + timestamp: + type: number + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + timestampReadable: + type: string + additionalProperties: + description: Contextual data is added to this object (such as topic data, etc.) + bans: + type: array + items: + type: object + properties: + uid: + type: number + timestamp: + type: number + expire: + type: number + fromUid: + type: number + user: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + until: + type: number + untilReadable: + type: string + timestampReadable: + type: string + timestampISO: + type: string + reason: + type: string + sessions: + type: array + items: + type: object + properties: + ip: + type: string + uuid: + type: string + datetime: + type: number + platform: + type: string + browser: + type: string + version: + type: string + current: + type: boolean + datetimeISO: + type: string + usernames: + type: array + items: + type: object + properties: + value: + type: string + timestamp: + type: number + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + emails: + type: array + items: + type: object + properties: + value: + type: string + timestamp: + type: number + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + moderationNotes: + type: array + items: + type: object + properties: + uid: + type: number + note: + type: string + timestamp: + type: number + timestampISO: + type: string + user: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + title: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/settings": + get: + tags: + - users + summary: /api/user/{userslug}/settings + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + settings: + type: object + properties: + showemail: + type: boolean + usePagination: + type: boolean + topicsPerPage: + type: number + postsPerPage: + type: number + topicPostSort: + type: string + openOutgoingLinksInNewTab: + type: boolean + dailyDigestFreq: + type: string + showfullname: + type: boolean + followTopicsOnCreate: + type: boolean + followTopicsOnReply: + type: boolean + restrictChat: + type: boolean + topicSearchEnabled: + type: boolean + categoryTopicSort: + type: string + userLang: + type: string + bootswatchSkin: + type: string + homePageRoute: + type: string + scrollToMyPost: + type: boolean + notificationSound: + type: string + incomingChatSound: + type: string + outgoingChatSound: + type: string + notificationType_new-chat: + type: string + notificationType_new-reply: + type: string + sendChatNotifications: + nullable: true + sendPostNotifications: + nullable: true + notificationType_upvote: + type: string + notificationType_new-topic: + type: string + notificationType_follow: + type: string + notificationType_group-invite: + type: string + upvoteNotifFreq: + type: string + notificationType_mention: + type: string + acpLang: + type: string + notificationType_new-register: + type: string + notificationType_post-queue: + type: string + notificationType_new-post-flag: + type: string + notificationType_new-user-flag: + type: string + categoryWatchState: + type: string + notificationType_group-request-membership: + type: string + uid: + type: number + description: A user identifier + required: + - showemail + - usePagination + - topicsPerPage + - postsPerPage + - topicPostSort + - openOutgoingLinksInNewTab + - dailyDigestFreq + - showfullname + - followTopicsOnCreate + - followTopicsOnReply + - restrictChat + - topicSearchEnabled + - categoryTopicSort + - userLang + - bootswatchSkin + - homePageRoute + - scrollToMyPost + - notificationType_new-chat + - notificationType_new-reply + - notificationType_upvote + - notificationType_new-topic + - notificationType_follow + - notificationType_group-invite + - upvoteNotifFreq + - acpLang + - notificationType_new-register + - notificationType_post-queue + - notificationType_new-post-flag + - notificationType_new-user-flag + - categoryWatchState + - notificationType_group-request-membership + - uid + languages: + type: array + items: + type: object + properties: + name: + type: string + code: + type: string + dir: + type: string + selected: + type: boolean + acpLanguages: + type: array + items: + type: object + properties: + name: + type: string + code: + type: string + dir: + type: string + selected: + type: boolean + notification-sound: + type: array + items: + type: object + properties: + name: + type: string + sounds: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + selected: + type: boolean + notificationSound: + type: array + items: + type: object + properties: + name: + type: string + selected: + type: boolean + chat-incoming-sound: + type: array + items: + type: object + properties: + name: + type: string + sounds: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + selected: + type: boolean + incomingChatSound: + type: array + items: + type: object + properties: + name: + type: string + selected: + type: boolean + chat-outgoing-sound: + type: array + items: + type: object + properties: + name: + type: string + sounds: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + selected: + type: boolean + outgoingChatSound: + type: array + items: + type: object + properties: + name: + type: string + selected: + type: boolean + customSettings: + type: array + items: + type: object + properties: {} + additionalProperties: {} + homePageRoutes: + type: array + items: + type: object + properties: + route: + type: string + name: + type: string + selected: + type: boolean + notificationSettings: + type: array + items: + type: object + properties: + name: + type: string + label: + type: string + none: + type: boolean + notification: + type: boolean + email: + type: boolean + notificationemail: + type: boolean + disableEmailSubscriptions: + type: number + dailyDigestFreqOptions: + type: array + items: + type: object + properties: + value: + type: string + name: + type: string + selected: + type: boolean + bootswatchSkinOptions: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + selected: + type: boolean + upvoteNotifFreq: + type: array + items: + type: object + properties: + name: + type: string + selected: + type: boolean + categoryWatchState: + type: object + properties: + watching: + type: boolean + disableCustomUserSkins: + type: number + allowUserHomePage: + type: number + hideFullname: + type: number + hideEmail: + type: number + inTopicSearchAvailable: + type: boolean + maxTopicsPerPage: + type: number + maxPostsPerPage: + type: number + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/uploads": + get: + tags: + - users + summary: /api/user/{userslug}/uploads + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + uploads: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + privateUploads: + type: boolean + title: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/consent": + get: + tags: + - users + summary: /api/user/{userslug}/consent + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + gdpr_consent: + type: boolean + digest: + type: object + properties: + frequency: + type: string + enabled: + type: boolean + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/blocks": + get: + tags: + - users + summary: /api/user/{userslug}/blocks + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + users: + type: array + items: + $ref: components/schemas/UserObject.yaml#/UserObjectSlim + title: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/sessions": + get: + tags: + - users + summary: /api/user/{userslug}/sessions + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - $ref: components/schemas/UserObject.yaml#/UserObjectFull + - type: object + properties: + sessions: + type: array + items: + type: object + properties: + ip: + type: string + uuid: + type: string + datetime: + type: number + platform: + type: string + browser: + type: string + version: + type: string + current: + type: boolean + datetimeISO: + type: string + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/session/{uuid}": + delete: + tags: + - users + summary: Revoke a user session + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + - name: uuid + in: path + required: true + schema: + type: string + example: testuuid + responses: + "200": + description: User session revoked + /api/notifications: + get: + tags: + - notifications + summary: /api/notifications + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + notifications: + type: array + items: + type: object + properties: + type: + type: string + bodyShort: + type: string + bodyLong: + type: string + pid: + oneOf: + - type: number + - type: string + tid: + type: number + description: A topic identifier + path: + type: string + nid: + type: string + from: + type: number + mergeId: + type: string + topicTitle: + type: string + importance: + type: number + datetime: + type: number + datetimeISO: + type: string + user: + type: object + properties: + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + uid: + type: number + description: A user identifier + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + image: + type: string + read: + type: boolean + readClass: + type: string + subject: + type: string + filters: + type: array + items: + type: object + additionalProperties: {} + regularFilters: + type: array + items: + type: object + properties: + name: + type: string + filter: + type: string + selected: + type: boolean + required: + - name + - filter + moderatorFilters: + type: array + items: + type: object + properties: + name: + type: string + filter: + type: string + selectedFilter: + type: object + properties: + name: + type: string + filter: + type: string + selected: + type: boolean + title: + type: string + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/user/{userslug}/chats/{roomid}": + get: + tags: + - users + summary: Get chat room + parameters: + - name: userslug + in: path + required: true + schema: + type: string + example: admin + - name: roomid + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + owner: + type: number + roomId: + type: number + roomName: + type: string + messages: + type: array + items: + type: object + properties: + content: + type: string + timestamp: + type: number + fromuid: + type: number + roomId: + type: string + deleted: + type: boolean + system: + type: boolean + edited: + type: number + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + editedISO: + type: string + messageId: + type: number + fromUser: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + type: string + nullable: true + status: + type: string + banned: + type: boolean + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + banned_until_readable: + type: string + deleted: + type: boolean + self: + type: number + newSet: + type: boolean + index: + type: number + cleanedContent: + type: string + isOwner: + type: boolean + isOwner: + type: boolean + users: + type: array + items: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + picture: + type: string + nullable: true + status: + type: string + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without an + avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with `icon:text` + for the user's auto-generated icon + example: "#f44336" + isOwner: + type: boolean + canReply: + type: boolean + groupChat: + type: boolean + usernames: + type: string + maximumUsersInChatRoom: + type: number + maximumChatMessageLength: + type: number + showUserInput: + type: boolean + isAdminOrGlobalMod: + type: boolean + rooms: + type: array + items: + type: object + properties: + owner: + oneOf: + - type: number + - type: string + roomId: + type: number + roomName: + type: string + users: + type: array + items: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + status: + type: string + lastonline: + type: number + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + lastonlineISO: + type: string + groupChat: + type: boolean + unread: + type: boolean + teaser: + type: object + properties: + fromuid: + type: number + content: + type: string + timestamp: + type: number + timestampISO: + type: string + description: An ISO 8601 formatted date string (complementing `timestamp`) + user: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + status: + type: string + lastonline: + type: number + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users + without an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's + auto-generated icon + example: "#f44336" + lastonlineISO: + type: string + nullable: true + lastUser: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + status: + type: string + lastonline: + type: number + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + lastonlineISO: + type: string + usernames: + type: string + nextStart: + type: number + title: + type: string + uid: + type: number + description: A user identifier + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + canViewInfo: + type: boolean + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/chats/{roomid}": + get: + tags: + - shorthand + summary: /api/chats/{roomid} + parameters: + - name: roomid + in: path + required: true + schema: + type: string + example: 1 + responses: + "200": + description: "Chat identifier resolved" + content: + text/plain: + schema: + type: string + description: A relative path to the canonical URL for that chat page + /api/users: + get: + tags: + - users + summary: /api/users + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + users: + type: array + items: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + picture: + nullable: true + type: string + status: + type: string + postcount: + type: number + reputation: + type: number + email:confirmed: + type: number + description: Whether the user has confirmed their email address or not + lastonline: + type: number + flags: + nullable: true + banned: + type: number + banned:expire: + type: number + joindate: + type: number + description: A UNIX timestamp representing the moment the user's account was + created + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without an + avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with `icon:text` + for the user's auto-generated icon + example: "#f44336" + joindateISO: + type: string + lastonlineISO: + type: string + banned_until: + type: number + banned_until_readable: + type: string + administrator: + type: boolean + userCount: + type: number + title: + type: string + isAdminOrGlobalMod: + type: boolean + isAdmin: + type: boolean + isGlobalMod: + type: boolean + displayUserSearch: + type: boolean + section_joindate: + type: boolean + maximumInvites: + type: number + inviteOnly: + type: boolean + adminInviteOnly: + type: boolean + invites: + type: number + showInviteButton: + type: boolean + reputation:disabled: + type: number + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + /api/groups: + get: + tags: + - groups + summary: /api/groups + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + groups: + type: array + items: + type: object + properties: + name: + type: string + description: + type: string + deleted: + oneOf: + - type: number + - type: string + hidden: + type: number + system: + type: number + userTitle: + type: string + icon: + type: string + labelColor: + type: string + createtime: + type: number + slug: + type: string + memberCount: + type: number + private: + type: number + userTitleEnabled: + type: number + disableJoinRequests: + type: number + disableLeave: + type: number + nameEncoded: + type: string + displayName: + type: string + textColor: + type: string + createtimeISO: + type: string + cover:thumb:url: + type: string + cover:url: + type: string + cover:position: + type: string + members: + type: array + items: + type: object + properties: + uid: + type: number + description: A user identifier + username: + type: string + description: A friendly name for a given user account + picture: + nullable: true + type: string + userslug: + type: string + description: An URL-safe variant of the username (i.e. lower-cased, spaces + removed, etc.) + icon:text: + type: string + description: A single-letter representation of a username. This is used in the + auto-generated icon given to users without + an avatar + icon:bgColor: + type: string + description: A six-character hexadecimal colour code assigned to the user. This + value is used in conjunction with + `icon:text` for the user's auto-generated + icon + example: "#f44336" + truncated: + type: boolean + ownerUid: + type: number + allowGroupCreation: + type: boolean + nextStart: + type: number + title: + type: string + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/groups/{slug}": + get: + tags: + - groups + summary: /api/groups/{slug} + parameters: + - name: slug + in: path + required: true + schema: + type: string + example: administrators + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + title: + type: string + group: + $ref: components/schemas/GroupObject.yaml#/GroupFullObject + posts: + $ref: components/schemas/PostsObject.yaml#/PostsObject + isAdmin: + type: boolean + isGlobalMod: + type: boolean + allowPrivateGroups: + type: number + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps + "/api/groups/{slug}/members": + get: + tags: + - groups + summary: Get user group members + parameters: + - name: slug + in: path + required: true + schema: + type: string + example: administrators + responses: + "200": + description: "" + content: + application/json: + schema: + allOf: + - type: object + properties: + users: + type: array + - $ref: components/schemas/Pagination.yaml#/Pagination + - $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs + - $ref: components/schemas/CommonProps.yaml#/CommonProps diff --git a/public/src/admin/general/dashboard.js b/public/src/admin/general/dashboard.js index 4d617a7348..b4d6afb797 100644 --- a/public/src/admin/general/dashboard.js +++ b/public/src/admin/general/dashboard.js @@ -234,6 +234,7 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress' id: 'left-y-axis', ticks: { beginAtZero: true, + precision: 0, }, type: 'linear', position: 'left', @@ -246,6 +247,7 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress' ticks: { beginAtZero: true, suggestedMax: 10, + precision: 0, }, type: 'linear', position: 'right', diff --git a/public/src/admin/manage/category-analytics.js b/public/src/admin/manage/category-analytics.js index c9d4d55c2e..089f34ae08 100644 --- a/public/src/admin/manage/category-analytics.js +++ b/public/src/admin/manage/category-analytics.js @@ -101,6 +101,7 @@ define('admin/manage/category-analytics', ['Chart'], function (Chart) { yAxes: [{ ticks: { beginAtZero: true, + precision: 0, }, }], }, @@ -120,6 +121,7 @@ define('admin/manage/category-analytics', ['Chart'], function (Chart) { yAxes: [{ ticks: { beginAtZero: true, + precision: 0, }, }], }, @@ -139,6 +141,7 @@ define('admin/manage/category-analytics', ['Chart'], function (Chart) { yAxes: [{ ticks: { beginAtZero: true, + precision: 0, }, }], }, @@ -158,6 +161,7 @@ define('admin/manage/category-analytics', ['Chart'], function (Chart) { yAxes: [{ ticks: { beginAtZero: true, + precision: 0, }, }], }, diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js index 882a860683..15c70655e0 100644 --- a/public/src/admin/manage/category.js +++ b/public/src/admin/manage/category.js @@ -281,35 +281,40 @@ define('admin/manage/category', [ } Category.launchParentSelector = function () { - var parents = [parseInt(ajaxify.data.category.cid, 10)]; - var categories = ajaxify.data.allCategories.filter(function (category) { - var isChild = parents.includes(parseInt(category.parentCid, 10)); - if (isChild) { - parents.push(parseInt(category.cid, 10)); + socket.emit('categories.getSelectCategories', {}, function (err, allCategories) { + if (err) { + return app.alertError(err.message); } - return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10) && !isChild; - }); - - categorySelector.modal(categories, function (parentCid) { - var payload = {}; - - payload[ajaxify.data.category.cid] = { - parentCid: parentCid, - }; - - socket.emit('admin.categories.update', payload, function (err) { - if (err) { - return app.alertError(err.message); + var parents = [parseInt(ajaxify.data.category.cid, 10)]; + var categories = allCategories.filter(function (category) { + var isChild = parents.includes(parseInt(category.parentCid, 10)); + if (isChild) { + parents.push(parseInt(category.cid, 10)); } - var parent = ajaxify.data.allCategories.filter(function (category) { - return category && parseInt(category.cid, 10) === parseInt(parentCid, 10); - }); - parent = parent[0]; + return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10) && !isChild; + }); - $('button[data-action="removeParent"]').parent().removeClass('hide'); - $('button[data-action="setParent"]').addClass('hide'); - var buttonHtml = ' ' + parent.name; - $('button[data-action="changeParent"]').html(buttonHtml).parent().removeClass('hide'); + categorySelector.modal(categories, function (parentCid) { + var payload = {}; + + payload[ajaxify.data.category.cid] = { + parentCid: parentCid, + }; + + socket.emit('admin.categories.update', payload, function (err) { + if (err) { + return app.alertError(err.message); + } + var parent = allCategories.filter(function (category) { + return category && parseInt(category.cid, 10) === parseInt(parentCid, 10); + }); + parent = parent[0]; + + $('button[data-action="removeParent"]').parent().removeClass('hide'); + $('button[data-action="setParent"]').addClass('hide'); + var buttonHtml = ' ' + parent.name; + $('button[data-action="changeParent"]').html(buttonHtml).parent().removeClass('hide'); + }); }); }); }; diff --git a/public/src/admin/manage/privileges.js b/public/src/admin/manage/privileges.js index 86b2351d36..91813b4626 100644 --- a/public/src/admin/manage/privileges.js +++ b/public/src/admin/manage/privileges.js @@ -140,6 +140,7 @@ define('admin/manage/privileges', [ modal.on('shown.bs.modal', function () { var inputEl = modal.find('input'); + inputEl.focus(); autocomplete.user(inputEl, function (ev, ui) { var defaultPrivileges = cid ? ['find', 'read', 'topics:read'] : ['chat']; diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js index e21360cf20..7fc70b7cce 100644 --- a/public/src/admin/manage/tags.js +++ b/public/src/admin/manage/tags.js @@ -91,10 +91,8 @@ define('admin/manage/tags', [ } var firstTag = $(tagsToModify[0]); - var title = tagsToModify.length > 1 ? '[[admin/manage/tags:alerts.editing-multiple]]' : '[[admin/manage/tags:alerts.editing-x, ' + firstTag.find('.tag-item').attr('data-tag') + ']]'; - var modal = bootbox.dialog({ - title: title, + title: '[[admin/manage/tags:alerts.editing]]', message: firstTag.find('.tag-modal').html(), buttons: { success: { @@ -140,11 +138,8 @@ define('admin/manage/tags', [ return; } - var firstTag = $(tagsToModify[0]); - var title = tagsToModify.length > 1 ? '[[admin/manage/tags:alerts.editing-multiple]]' : '[[admin/manage/tags:alerts.editing-x, ' + firstTag.find('.tag-item').attr('data-tag') + ']]'; - var modal = bootbox.dialog({ - title: title, + title: '[[admin/manage/tags:alerts.editing]]', message: $('.rename-modal').html(), buttons: { success: { diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index eef65cccab..ca4e63dc41 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -73,7 +73,7 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct } Benchpress.parse('admin/partials/manage_user_groups', data, function (html) { var modal = bootbox.dialog({ - message: utils.escapeHTML(html), + message: html, title: '[[admin/manage/users:manage-groups]]', onEscape: true, }); @@ -407,6 +407,7 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct app.alertSuccess('[[admin/manage/users:alerts.email-sent-to, ' + email + ']]'); }); }); + return false; }); } diff --git a/public/src/app.js b/public/src/app.js index f8a3a643ef..70375a8869 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -500,7 +500,7 @@ app.cacheBuster = null; titleObj.interval = setInterval(function () { var title = titleObj.titles[titleObj.titles.indexOf(window.document.title) ^ 1]; if (title) { - window.document.title = $('

').html(title).text(); + window.document.title = $('
').html(title).text(); } }, 2000); }); @@ -510,7 +510,7 @@ app.cacheBuster = null; clearInterval(titleObj.interval); } if (titleObj.titles[0]) { - window.document.title = $('
').html(titleObj.titles[0]).text(); + window.document.title = $('
').html(titleObj.titles[0]).text(); } } }; diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js index 3e973c68a3..f219a399af 100644 --- a/public/src/client/category/tools.js +++ b/public/src/client/category/tools.js @@ -254,9 +254,15 @@ define('forum/category/tools', [ } function handlePinnedTopicSort() { - if (!ajaxify.data.privileges.isAdminOrMod) { + var numPinned = ajaxify.data.topics.reduce(function (memo, topic) { + memo = topic.pinned ? memo += 1 : memo; + return memo; + }, 0); + + if (!ajaxify.data.privileges.isAdminOrMod || numPinned < 2) { return; } + app.loadJQueryUI(function () { var topicListEl = $('[component="category"]').filter(function (i, e) { return !$(e).parents('[widget-area],[data-widget-area]').length; diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 1d7fb2646e..a2a0083208 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -194,6 +194,9 @@ define('forum/chats', [ if (e.target === components.get('chat/input').get(0)) { // Retrieve message id from messages list var message = components.get('chat/messages').find('.chat-message[data-self="1"]').last(); + if (!message.length) { + return; + } var lastMid = message.attr('data-mid'); var inputEl = components.get('chat/input'); @@ -485,7 +488,7 @@ define('forum/chats', [ app.updateUserStatus($('.chats-list [data-uid="' + data.uid + '"] [component="user/status"]'), data.status); }); - messages.onChatMessageEdit(); + messages.addSocketListeners(); socket.on('event:chats.roomRename', function (data) { var roomEl = components.get('chat/recent/room', data.roomId); diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index 4f87bf5d14..8e280e830c 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -129,14 +129,20 @@ define('forum/chats/messages', ['components', 'sounds', 'translator', 'benchpres // By setting the `data-mid` attribute, I tell the chat code that I am editing a // message, instead of posting a new one. inputEl.attr('data-mid', messageId).addClass('editing'); - inputEl.val(raw); + inputEl.val(raw).focus(); } }); }; - messages.onChatMessageEdit = function () { + messages.addSocketListeners = function () { socket.removeListener('event:chats.edit', onChatMessageEdited); socket.on('event:chats.edit', onChatMessageEdited); + + socket.removeListener('event:chats.delete', onChatMessageDeleted); + socket.on('event:chats.delete', onChatMessageDeleted); + + socket.removeListener('event:chats.restore', onChatMessageRestored); + socket.on('event:chats.restore', onChatMessageRestored); }; function onChatMessageEdited(data) { @@ -153,6 +159,18 @@ define('forum/chats/messages', ['components', 'sounds', 'translator', 'benchpres }); } + function onChatMessageDeleted(messageId) { + components.get('chat/message', messageId) + .toggleClass('deleted', true) + .find('[component="chat/message/body"]').translateHtml('[[modules:chat.message-deleted]]'); + } + + function onChatMessageRestored(message) { + components.get('chat/message', message.messageId) + .toggleClass('deleted', false) + .find('[component="chat/message/body"]').html(message.content); + } + messages.delete = function (messageId, roomId) { translator.translate('[[modules:chat.delete_message_confirm]]', function (translated) { bootbox.confirm(translated, function (ok) { diff --git a/public/src/client/chats/search.js b/public/src/client/chats/search.js index e40551ed05..db267cfa07 100644 --- a/public/src/client/chats/search.js +++ b/public/src/client/chats/search.js @@ -64,7 +64,7 @@ define('forum/chats/search', ['components'], function (components) { ' ' + userObj.username; } - var chatEl = $('
  • ') + var chatEl = $('
  • ') .attr('data-uid', userObj.uid) .appendTo(chatsListEl); diff --git a/public/src/client/flags/list.js b/public/src/client/flags/list.js index 16a5535e7d..9c5dc63976 100644 --- a/public/src/client/flags/list.js +++ b/public/src/client/flags/list.js @@ -23,7 +23,7 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart) var payload = filtersEl.serializeArray().filter(function (item) { return !!item.value; }); - ajaxify.go('flags?' + $.param(payload)); + ajaxify.go('flags?' + (payload.length ? $.param(payload) : 'reset=1')); }); }; @@ -74,6 +74,7 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart) yAxes: [{ ticks: { beginAtZero: true, + precision: 0, }, }], }, diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js index 1a7171dd0c..c1e181f2ab 100644 --- a/public/src/client/infinitescroll.js +++ b/public/src/client/infinitescroll.js @@ -7,6 +7,7 @@ define('forum/infinitescroll', function () { var previousScrollTop = 0; var loadingMore = false; var container; + var scrollTimeout = 0; scroll.init = function (el, cb) { if (typeof el === 'function') { @@ -17,9 +18,19 @@ define('forum/infinitescroll', function () { container = el || $('body'); } previousScrollTop = $(window).scrollTop(); - $(window).off('scroll', onScroll).on('scroll', onScroll); + $(window).off('scroll', startScrollTimeout).on('scroll', startScrollTimeout); }; + function startScrollTimeout() { + if (scrollTimeout) { + clearTimeout(scrollTimeout); + } + scrollTimeout = setTimeout(function () { + scrollTimeout = 0; + onScroll(); + }, 60); + } + function onScroll() { var bsEnv = utils.findBootstrapEnvironment(); var mobileComposerOpen = (bsEnv === 'xs' || bsEnv === 'sm') && $('html').hasClass('composing'); @@ -32,9 +43,8 @@ define('forum/infinitescroll', function () { var offsetTop = container.offset() ? container.offset().top : 0; var scrollPercent = 100 * (currentScrollTop - offsetTop) / (viewportHeight <= 0 ? wh : viewportHeight); - var top = 20; - var bottom = 80; - + var top = 15; + var bottom = 85; var direction = currentScrollTop > previousScrollTop ? 1 : -1; if (scrollPercent < top && currentScrollTop < previousScrollTop) { diff --git a/public/src/client/login.js b/public/src/client/login.js index b571ebf021..317e61ae3d 100644 --- a/public/src/client/login.js +++ b/public/src/client/login.js @@ -41,6 +41,7 @@ define('forum/login', [], function () { app.updateHeader(data, function () { ajaxify.go(data.next); + app.flags._sessionRefresh = false; $(window).trigger('action:app.loggedIn', data); }); }, diff --git a/public/src/client/search.js b/public/src/client/search.js index 54e81242a3..f2d658edf7 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -148,7 +148,7 @@ define('forum/search', ['search', 'autocomplete', 'storage'], function (searchMo result.find('*').each(function () { $(this).after(''); - nested.push($('
    ').append($(this))); + nested.push($('
    ').append($(this))); }); result.html(result.html().replace(regex, function (match, p1) { diff --git a/public/src/client/topic.js b/public/src/client/topic.js index a26f9f6a95..46df30aa63 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -87,7 +87,7 @@ define('forum/topic', [ if (config.topicSearchEnabled) { require(['mousetrap'], function (mousetrap) { - mousetrap.bind('ctrl+f', function (e) { + mousetrap.bind(['command+f', 'ctrl+f'], function (e) { var match = ajaxify.currentPage.match(/^topic\/([\d]+)/); var tid; if (match) { @@ -241,6 +241,9 @@ define('forum/topic', [ function updateUserBookmark(index) { var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark'; var currentBookmark = ajaxify.data.bookmark || storage.getItem(bookmarkKey); + if (config.topicPostSort === 'newest_to_oldest') { + index = Math.max(1, ajaxify.data.postcount - index + 2); + } if (ajaxify.data.postcount > ajaxify.data.bookmarkThreshold && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10) || ajaxify.data.postcount < parseInt(currentBookmark, 10))) { if (app.user.uid) { diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 6bcc05ddc2..de94534384 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -28,6 +28,8 @@ define('forum/topic/posts', [ updatePostCounts(data.posts); + updatePostIndices(data.posts); + ajaxify.data.postcount += 1; postTools.updatePostCount(ajaxify.data.postcount); @@ -61,6 +63,16 @@ define('forum/topic/posts', [ } } + function updatePostIndices(posts) { + if (config.topicPostSort === 'newest_to_oldest') { + posts[0].index = 1; + components.get('post').not('[data-index=0]').each(function () { + var newIndex = parseInt($(this).attr('data-index'), 10) + 1; + $(this).attr('data-index', newIndex); + }); + } + } + function onNewPostPagination(data) { function scrollToPost() { scrollToPostIfSelf(data.posts[0]); @@ -88,7 +100,7 @@ define('forum/topic/posts', [ function updatePagination() { $.get(config.relative_path + '/api/topic/pagination/' + ajaxify.data.tid, { page: ajaxify.data.pagination.currentPage }, function (paginationData) { - app.parseAndTranslate('partials/paginator', { pagination: paginationData }, function (html) { + app.parseAndTranslate('partials/paginator', paginationData, function (html) { $('[component="pagination"]').after(html).remove(); }); }); @@ -211,13 +223,10 @@ define('forum/topic/posts', [ } Posts.loadMorePosts = function (direction) { - if (!components.get('topic').length || navigator.scrollActive || Posts._infiniteScrollTimeout) { + if (!components.get('topic').length || navigator.scrollActive) { return; } - Posts._infiniteScrollTimeout = setTimeout(function () { - delete Posts._infiniteScrollTimeout; - }, 1000); var replies = components.get('topic').find(components.get('post').not('[data-index=0]').not('.new')); var afterEl = direction > 0 ? replies.last() : replies.first(); var after = parseInt(afterEl.attr('data-index'), 10) || 0; diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js index 3a52470171..11d9adb5a0 100644 --- a/public/src/client/topic/threadTools.js +++ b/public/src/client/topic/threadTools.js @@ -153,7 +153,9 @@ define('forum/topic/threadTools', [ } app.parseAndTranslate('partials/topic/topic-menu-list', data, function (html) { dropdownMenu.html(html); - $(window).trigger('action:topic.tools.load'); + $(window).trigger('action:topic.tools.load', { + element: dropdownMenu, + }); }); }); }); diff --git a/public/src/modules/autocomplete.js b/public/src/modules/autocomplete.js index 9baf4329f5..2f2f9fef04 100644 --- a/public/src/modules/autocomplete.js +++ b/public/src/modules/autocomplete.js @@ -26,7 +26,7 @@ define('autocomplete', function () { if (result && result.users) { var names = result.users.map(function (user) { - var username = $('
    ').html(user.username).text(); + var username = $('
    ').html(user.username).text(); return user && { label: username, value: username, diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 46ad454585..46549ea620 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -121,7 +121,7 @@ define('chat', [ }; module.onRoomRename = function (data) { - var newTitle = $('
    ').html(data.newName).text(); + var newTitle = $('
    ').html(data.newName).text(); var modal = module.getModal(data.roomId); modal.find('[component="chat/room/name"]').text(newTitle); taskbar.update('chat', modal.attr('data-uuid'), { @@ -242,7 +242,7 @@ define('chat', [ Chats.addCharactersLeftHandler(chatModal); Chats.addIPHandler(chatModal); - ChatsMessages.onChatMessageEdit(); + ChatsMessages.addSocketListeners(); taskbar.push('chat', chatModal.attr('data-uuid'), { title: '[[modules:chat.chatting_with]] ' + (data.roomName || (data.users.length ? data.users[0].username : '')), diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 943316bfd3..994d062f89 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -287,6 +287,7 @@ 'alt="' + userObj.username + '"', 'title="' + userObj.username + '"', 'data-uid="' + userObj.uid + '"', + 'loading="lazy"', ]; var styles = []; classNames = classNames || ''; diff --git a/public/src/modules/iconSelect.js b/public/src/modules/iconSelect.js index dfc190f28d..e47eb2cb56 100644 --- a/public/src/modules/iconSelect.js +++ b/public/src/modules/iconSelect.js @@ -46,7 +46,7 @@ define('iconSelect', ['benchpress'], function (Benchpress) { className: 'btn-primary', callback: function () { var iconClass = $('.bootbox .selected').attr('class'); - var categoryIconClass = $('
    ').addClass(iconClass).removeClass('fa').removeClass('selected') + var categoryIconClass = $('
    ').addClass(iconClass).removeClass('fa').removeClass('selected') .attr('class'); if (categoryIconClass) { diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index ab4ead6158..106cf7ad6a 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -10,6 +10,7 @@ define('settings', function () { 'settings/array', 'settings/key', 'settings/object', + 'settings/sorted-list', ]; var Settings; @@ -271,6 +272,25 @@ define('settings', function () { onReady.push(callback); } }, + serializeForm: function (formEl) { + var values = formEl.serializeObject(); + + // "Fix" checkbox values, so that unchecked options are not omitted + formEl.find('input[type="checkbox"]').each(function (idx, inputEl) { + inputEl = $(inputEl); + if (!inputEl.is(':checked')) { + values[inputEl.attr('name')] = 'off'; + } + }); + + // save multiple selects as json arrays + formEl.find('select[multiple]').each(function (idx, selectEl) { + selectEl = $(selectEl); + values[selectEl.attr('name')] = JSON.stringify(selectEl.val()); + }); + + return values; + }, /** Persists the given settings with given hash. @param hash The hash to use as settings-id. @@ -456,7 +476,6 @@ define('settings', function () { if (err) { return callback(err); } - // multipe selects are saved as json arrays, parse them here $(formEl).find('select[multiple]').each(function (idx, selectEl) { var key = $(selectEl).attr('name'); @@ -472,6 +491,12 @@ define('settings', function () { // Save loaded settings into ajaxify.data for use client-side ajaxify.data.settings = values; + helper.whenReady(function () { + $(formEl).find('[data-sorted-list]').each(function (idx, el) { + getHook(el, 'get').call(Settings, $(el), hash); + }); + }); + $(formEl).deserialize(values); $(formEl).find('input[type="checkbox"]').each(function () { $(this).parents('.mdl-switch').toggleClass('is-checked', $(this).is(':checked')); @@ -489,23 +514,17 @@ define('settings', function () { }, save: function (hash, formEl, callback) { formEl = $(formEl); + if (formEl.length) { - var values = formEl.serializeObject(); + var values = helper.serializeForm(formEl); - // "Fix" checkbox values, so that unchecked options are not omitted - formEl.find('input[type="checkbox"]').each(function (idx, inputEl) { - inputEl = $(inputEl); - if (!inputEl.is(':checked')) { - values[inputEl.attr('name')] = 'off'; + helper.whenReady(function () { + var list = formEl.find('[data-sorted-list]'); + if (list.length) { + getHook(list, 'set').call(Settings, list, values); } }); - // save multiple selects as json arrays - formEl.find('select[multiple]').each(function (idx, selectEl) { - selectEl = $(selectEl); - values[selectEl.attr('name')] = JSON.stringify(selectEl.val()); - }); - socket.emit('admin.settings.set', { hash: hash, values: values, diff --git a/public/src/modules/settings/sorted-list.js b/public/src/modules/settings/sorted-list.js new file mode 100644 index 0000000000..da638ca5e6 --- /dev/null +++ b/public/src/modules/settings/sorted-list.js @@ -0,0 +1,125 @@ +'use strict'; + +define('settings/sorted-list', ['benchpress', 'jqueryui'], function (benchpress) { + var SortedList; + var Settings; + + + SortedList = { + types: ['sorted-list'], + use: function () { + Settings = this; + }, + set: function ($container, values) { + var key = $container.attr('data-sorted-list'); + + values[key] = []; + $container.find('[data-type="item"]').each(function (idx, item) { + var itemUUID = $(item).attr('data-sorted-list-uuid'); + + var formData = $('[data-sorted-list-object="' + key + '"][data-sorted-list-uuid="' + itemUUID + '"]'); + values[key].push(Settings.helper.serializeForm(formData)); + }); + }, + get: function ($container) { + var $list = $container.find('[data-type="list"]'); + var key = $container.attr('data-sorted-list'); + var formTpl = $container.attr('data-form-template'); + + benchpress.parse(formTpl, {}, function (formHtml) { + var addBtn = $('[data-sorted-list="' + key + '"] [data-type="add"]'); + + addBtn.on('click', function () { + var modal = bootbox.confirm(formHtml, function (save) { + if (save) { + var itemUUID = utils.generateUUID(); + var form = $('
    '); + form.append(modal.find('form').children()); + + $('#content').append(form.hide()); + + + var data = Settings.helper.serializeForm(form); + parse($container, itemUUID, data); + } + }); + }); + + var list = ajaxify.data.settings[key]; + if (Array.isArray(list) && typeof list[0] !== 'string') { + list.forEach(function (item) { + var itemUUID = utils.generateUUID(); + var form = $(formHtml).deserialize(item); + form.attr('data-sorted-list-uuid', itemUUID); + form.attr('data-sorted-list-object', key); + $('#content').append(form.hide()); + + parse($container, itemUUID, item); + }); + } + }); + + $list.sortable().addClass('pointer'); + }, + }; + + function setupRemoveButton($container, itemUUID) { + var key = $container.attr('data-sorted-list'); + + var removeBtn = $('[data-sorted-list="' + key + '"] [data-type="remove"]'); + removeBtn.on('click', function () { + $('[data-sorted-list-uuid="' + itemUUID + '"]').remove(); + }); + } + + function setupEditButton($container, itemUUID) { + var $list = $container.find('[data-type="list"]'); + var key = $container.attr('data-sorted-list'); + var itemTpl = $container.attr('data-item-template'); + var editBtn = $('[data-sorted-list-uuid="' + itemUUID + '"] [data-type="edit"]'); + + editBtn.on('click', function () { + var form = $('[data-sorted-list-uuid="' + itemUUID + '"][data-sorted-list-object="' + key + '"]').clone(true).show(); + + var modal = bootbox.confirm(form, function (save) { + if (save) { + var form = $('
    '); + form.append(modal.find('form').children()); + + $('#content').find('[data-sorted-list-uuid="' + itemUUID + '"][data-sorted-list-object="' + key + '"]').remove(); + $('#content').append(form.hide()); + + + var data = Settings.helper.serializeForm(form); + + benchpress.parse(itemTpl, data, function (itemHtml) { + itemHtml = $(itemHtml); + var oldItem = $list.find('[data-sorted-list-uuid="' + itemUUID + '"]'); + oldItem.after(itemHtml); + oldItem.remove(); + itemHtml.attr('data-sorted-list-uuid', itemUUID); + + setupRemoveButton($container, itemUUID); + setupEditButton($container, itemUUID); + }); + } + }); + }); + } + + function parse($container, itemUUID, data) { + var $list = $container.find('[data-type="list"]'); + var itemTpl = $container.attr('data-item-template'); + + benchpress.parse(itemTpl, data, function (itemHtml) { + itemHtml = $(itemHtml); + $list.append(itemHtml); + itemHtml.attr('data-sorted-list-uuid', itemUUID); + + setupRemoveButton($container, itemUUID); + setupEditButton($container, itemUUID); + }); + } + + return SortedList; +}); diff --git a/public/src/modules/taskbar.js b/public/src/modules/taskbar.js index ad4a31d4ce..7aadf30307 100644 --- a/public/src/modules/taskbar.js +++ b/public/src/modules/taskbar.js @@ -153,7 +153,7 @@ define('taskbar', ['benchpress', 'translator'], function (Benchpress, translator translator.translate(data.options.title, function (taskTitle) { var title = $('
    ').text(taskTitle || 'NodeBB Task').html(); - var taskbarEl = $('
  • ') + var taskbarEl = $('
  • ') .addClass(data.options.className) .html('' + (data.options.icon ? ' ' : '') + @@ -200,6 +200,9 @@ define('taskbar', ['benchpress', 'translator'], function (Benchpress, translator taskbar.update = function (module, uuid, options) { var element = taskbar.tasklist.find('[data-module="' + module + '"][data-uuid="' + uuid + '"]'); + if (!element.length) { + return; + } var data = element.data(); Object.keys(options).forEach(function (key) { diff --git a/public/src/sockets.js b/public/src/sockets.js index 1c0ab6cf6d..5a1936e7fa 100644 --- a/public/src/sockets.js +++ b/public/src/sockets.js @@ -17,6 +17,25 @@ app.isConnected = false; socket = io(config.websocketAddress, ioParams); + var oEmit = socket.emit; + socket.emit = function (event, data, callback) { + if (typeof data === 'function') { + callback = data; + data = null; + } + if (typeof callback === 'function') { + oEmit.apply(socket, [event, data, callback]); + return; + } + + return new Promise(function (resolve, reject) { + oEmit.apply(socket, [event, data, function (err, result) { + if (err) reject(err); + else resolve(result); + }]); + }); + }; + if (parseInt(app.user.uid, 10) >= 0) { addHandlers(); } diff --git a/public/vendor/bootbox/bootbox.js b/public/vendor/bootbox/bootbox.js index 15a5527f4a..a814c5eb3c 100644 --- a/public/vendor/bootbox/bootbox.js +++ b/public/vendor/bootbox/bootbox.js @@ -464,7 +464,7 @@ if (option.group) { // initialise group if necessary if (!groups[option.group]) { - groups[option.group] = $("").attr("label", option.group); + groups[option.group] = $("").attr("label", option.group); } elem = groups[option.group]; @@ -496,7 +496,7 @@ // checkboxes have to nest within a containing element, so // they break the rules a bit and we end up re-assigning // our 'input' element to this container instead - input = $("
    "); + input = $("
    "); each(inputOptions, function(_, option) { var checkbox = $(templates.inputs[options.inputType]); diff --git a/public/vendor/redoc/index.html b/public/vendor/redoc/index.html new file mode 100644 index 0000000000..342866d600 --- /dev/null +++ b/public/vendor/redoc/index.html @@ -0,0 +1,24 @@ + + + + ReDoc + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/analytics.js b/src/analytics.js index 0fc71b7496..914a579fa9 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -148,6 +148,11 @@ Analytics.writeData = async function () { }; Analytics.getHourlyStatsForSet = async function (set, hour, numHours) { + // Guard against accidental ommission of `analytics:` prefix + if (!set.startsWith('analytics:')) { + set = 'analytics:' + set; + } + const terms = {}; const hoursArr = []; @@ -176,6 +181,11 @@ Analytics.getHourlyStatsForSet = async function (set, hour, numHours) { }; Analytics.getDailyStatsForSet = async function (set, day, numDays) { + // Guard against accidental ommission of `analytics:` prefix + if (!set.startsWith('analytics:')) { + set = 'analytics:' + set; + } + const daysArr = []; day = new Date(day); day.setDate(day.getDate() + 1); // set the date to tomorrow, because getHourlyStatsForSet steps *backwards* 24 hours to sum up the values @@ -183,7 +193,7 @@ Analytics.getDailyStatsForSet = async function (set, day, numDays) { while (numDays > 0) { /* eslint-disable no-await-in-loop */ - const dayData = await Analytics.getHourlyStatsForSet(set, day.getTime() - (1000 * 60 * 60 * 24 * numDays), 24); + const dayData = await Analytics.getHourlyStatsForSet(set, day.getTime() - (1000 * 60 * 60 * 24 * (numDays - 1)), 24); daysArr.push(dayData.reduce((cur, next) => cur + next)); numDays -= 1; } diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index 3e94cbf834..4ae423f6ac 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -1,13 +1,14 @@ 'use strict'; -var _ = require('lodash'); +const _ = require('lodash'); -var db = require('../database'); -var posts = require('../posts'); -var topics = require('../topics'); -var privileges = require('../privileges'); -var batch = require('../batch'); +const db = require('../database'); +const posts = require('../posts'); +const topics = require('../topics'); +const privileges = require('../privileges'); +const plugins = require('../plugins'); +const batch = require('../batch'); module.exports = function (Categories) { Categories.getRecentReplies = async function (cid, uid, count) { @@ -25,15 +26,17 @@ module.exports = function (Categories) { db.getObjectField('category:' + cid, 'numRecentReplies'), ]); - if (count < numRecentReplies) { - return await db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid); + if (count >= numRecentReplies) { + const data = await db.getSortedSetRangeWithScores('cid:' + cid + ':recent_tids', 0, count - numRecentReplies); + const shouldRemove = !(data.length === 1 && count === 1 && data[0].value === String(tid)); + if (data.length && shouldRemove) { + await db.sortedSetsRemoveRangeByScore(['cid:' + cid + ':recent_tids'], '-inf', data[data.length - 1].score); + } } - const data = await db.getSortedSetRangeWithScores('cid:' + cid + ':recent_tids', 0, count - numRecentReplies); - const shouldRemove = !(data.length === 1 && count === 1 && data[0].value === String(tid)); - if (data.length && shouldRemove) { - await db.sortedSetsRemoveRangeByScore(['cid:' + cid + ':recent_tids'], '-inf', data[data.length - 1].score); + if (numRecentReplies > 0) { + await db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid); } - await db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid); + await plugins.fireHook('action:categories.updateRecentTid', { cid: cid, tid: tid }); }; Categories.updateRecentTidForCid = async function (cid) { diff --git a/src/cli/index.js b/src/cli/index.js index fa7534fbbe..a1df792edf 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -82,10 +82,9 @@ program .version(pkg.version) .option('--json-logging', 'Output to logs in JSON format', false) .option('--log-level ', 'Default logging level to use', 'info') + .option('--config ', 'Specify a config file', 'config.json') .option('-d, --dev', 'Development mode, including verbose logging', false) - .option('-l, --log', 'Log subprocess output to console', false) - .option('-c, --config ', 'Specify a config file', 'config.json') - .parse(process.argv); + .option('-l, --log', 'Log subprocess output to console', false); nconf.argv().env({ separator: '__', @@ -98,7 +97,7 @@ global.env = env; prestart.setupWinston(); // Alternate configuration file support -var configFile = path.resolve(dirname, program.config); +var configFile = path.resolve(dirname, nconf.get('config') || 'config.json'); var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database')); prestart.loadConfig(configFile); @@ -167,6 +166,7 @@ program program .command('setup [config]') .description('Run the NodeBB setup script, or setup with an initial config') + .option('--skip-build', 'Run setup without building assets') .action(function (initConfig) { if (initConfig) { try { diff --git a/src/cli/package-install.js b/src/cli/package-install.js index 699a5962bd..acc61fcdb7 100644 --- a/src/cli/package-install.js +++ b/src/cli/package-install.js @@ -1,15 +1,15 @@ 'use strict'; -var path = require('path'); -var fs = require('fs'); -var cproc = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const cproc = require('child_process'); -var packageFilePath = path.join(__dirname, '../../package.json'); -var packageDefaultFilePath = path.join(__dirname, '../../install/package.json'); -var modulesPath = path.join(__dirname, '../../node_modules'); +const packageFilePath = path.join(__dirname, '../../package.json'); +const packageDefaultFilePath = path.join(__dirname, '../../install/package.json'); +const modulesPath = path.join(__dirname, '../../node_modules'); function updatePackageFile() { - var oldPackageContents = {}; + let oldPackageContents = {}; try { oldPackageContents = JSON.parse(fs.readFileSync(packageFilePath, 'utf8')); @@ -19,22 +19,42 @@ function updatePackageFile() { } } - var defaultPackageContents = JSON.parse(fs.readFileSync(packageDefaultFilePath, 'utf8')); - var packageContents = { ...oldPackageContents, ...defaultPackageContents, dependencies: { ...oldPackageContents.dependencies, ...defaultPackageContents.dependencies } }; + const defaultPackageContents = JSON.parse(fs.readFileSync(packageDefaultFilePath, 'utf8')); + const packageContents = { ...oldPackageContents, ...defaultPackageContents, dependencies: { ...oldPackageContents.dependencies, ...defaultPackageContents.dependencies } }; fs.writeFileSync(packageFilePath, JSON.stringify(packageContents, null, 2)); } exports.updatePackageFile = updatePackageFile; +exports.supportedPackageManager = [ + 'npm', + 'cnpm', + 'pnpm', + 'yarn', +]; + function installAll() { - var prod = global.env !== 'development'; - var command = 'npm install'; + const prod = global.env !== 'development'; + let command = 'npm install'; try { fs.accessSync(path.join(modulesPath, 'nconf/package.json'), fs.constants.R_OK); - var packageManager = require('nconf').get('package_manager'); - if (packageManager === 'yarn') { - command = 'yarn'; + const supportedPackageManagerList = exports.supportedPackageManager; // load config from src/cli/package-install.js + const packageManager = require('nconf').get('package_manager'); + if (supportedPackageManagerList.indexOf(packageManager) >= 0) { + switch (packageManager) { + case 'yarn': + command = 'yarn'; + break; + case 'pnpm': + command = 'pnpm install'; + break; + case 'cnpm': + command = 'cnpm install'; + break; + default: + break; + } } } catch (e) { // ignore @@ -63,13 +83,13 @@ function preserveExtraneousPlugins() { return; } - var isPackage = /^nodebb-(plugin|theme|widget|reward)-\w+/; - var packages = fs.readdirSync(modulesPath).filter(function (pkgName) { + const isPackage = /^nodebb-(plugin|theme|widget|reward)-\w+/; + const packages = fs.readdirSync(modulesPath).filter(function (pkgName) { return isPackage.test(pkgName); }); - var packageContents = JSON.parse(fs.readFileSync(packageFilePath, 'utf8')); + const packageContents = JSON.parse(fs.readFileSync(packageFilePath, 'utf8')); - var extraneous = packages + const extraneous = packages // only extraneous plugins (ones not in package.json) which are not links .filter(function (pkgName) { const extraneous = !packageContents.dependencies.hasOwnProperty(pkgName); @@ -79,7 +99,7 @@ function preserveExtraneousPlugins() { }) // reduce to a map of package names to package versions .reduce(function (map, pkgName) { - var pkgConfig = JSON.parse(fs.readFileSync(path.join(modulesPath, pkgName, 'package.json'), 'utf8')); + const pkgConfig = JSON.parse(fs.readFileSync(path.join(modulesPath, pkgName, 'package.json'), 'utf8')); map[pkgName] = pkgConfig.version; return map; }, {}); diff --git a/src/cli/setup.js b/src/cli/setup.js index 509de52ddb..897356b945 100644 --- a/src/cli/setup.js +++ b/src/cli/setup.js @@ -31,9 +31,13 @@ function setup(initConfig) { } prestart.loadConfig(configFile); - next(); + + if (!nconf.get('skip-build')) { + build.buildAll(next); + } else { + setImmediate(next); + } }, - build.buildAll, ], function (err, data) { // Disregard build step data data = data[0]; diff --git a/src/cli/upgrade-plugins.js b/src/cli/upgrade-plugins.js index 7a874d5e7d..8f2c240eea 100644 --- a/src/cli/upgrade-plugins.js +++ b/src/cli/upgrade-plugins.js @@ -1,28 +1,30 @@ 'use strict'; -var async = require('async'); -var prompt = require('prompt'); -var request = require('request'); -var cproc = require('child_process'); -var semver = require('semver'); -var fs = require('fs'); -var path = require('path'); -var nconf = require('nconf'); +const async = require('async'); +const prompt = require('prompt'); +const request = require('request'); +const cproc = require('child_process'); +const semver = require('semver'); +const fs = require('fs'); +const path = require('path'); +const nconf = require('nconf'); -var paths = require('./paths'); +const paths = require('./paths'); +const packageManager = nconf.get('package_manager'); -var packageManager = nconf.get('package_manager'); -var packageManagerExecutable = packageManager === 'yarn' ? 'yarn' : 'npm'; -var packageManagerInstallArgs = packageManager === 'yarn' ? ['add'] : ['install', '--save']; +const supportedPackageManagerList = require('./package-install').supportedPackageManager; // load config from src/cli/package-install.js + +let packageManagerExecutable = supportedPackageManagerList.indexOf(packageManager) >= 0 ? packageManager : 'npm'; +const packageManagerInstallArgs = packageManager === 'yarn' ? ['add'] : ['install', '--save']; if (process.platform === 'win32') { packageManagerExecutable += '.cmd'; } -var dirname = paths.baseDir; +const dirname = paths.baseDir; function getModuleVersions(modules, callback) { - var versionHash = {}; + const versionHash = {}; async.eachLimit(modules, 50, function (module, next) { fs.readFile(path.join(dirname, 'node_modules', module, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) { @@ -53,8 +55,8 @@ function getInstalledPlugins(callback) { return callback(err); } - var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/; - var checklist; + const isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/; + payload.files = payload.files.filter(function (file) { return isNbbModule.test(file); @@ -75,7 +77,7 @@ function getInstalledPlugins(callback) { }); // Whittle down deps to send back only extraneously installed plugins/themes/etc - checklist = payload.deps.filter(function (pkgName) { + const checklist = payload.deps.filter(function (pkgName) { if (payload.bundled.includes(pkgName)) { return false; } @@ -119,7 +121,7 @@ function checkPlugins(standalone, callback) { version: getCurrentVersion, }), function (payload, next) { - var toCheck = Object.keys(payload.plugins); + const toCheck = Object.keys(payload.plugins); if (!toCheck.length) { process.stdout.write(' OK'.green + ''.reset); @@ -141,9 +143,9 @@ function checkPlugins(standalone, callback) { body = [body]; } - var current; - var suggested; - var upgradable = body.map(function (suggestObj) { + let current; + let suggested; + const upgradable = body.map(function (suggestObj) { current = payload.plugins[suggestObj.package]; suggested = suggestObj.version; @@ -164,7 +166,7 @@ function checkPlugins(standalone, callback) { } function upgradePlugins(callback) { - var standalone = false; + let standalone = false; if (typeof callback !== 'function') { callback = function () {}; standalone = true; @@ -203,7 +205,7 @@ function upgradePlugins(callback) { if (['y', 'Y', 'yes', 'YES'].includes(result.upgrade)) { console.log('\nUpgrading packages...'); - var args = packageManagerInstallArgs.concat(found.map(function (suggestObj) { + const args = packageManagerInstallArgs.concat(found.map(function (suggestObj) { return suggestObj.name + '@' + suggestObj.suggested; })); diff --git a/src/controllers/accounts/consent.js b/src/controllers/accounts/consent.js index 9038b2905a..a4989ff31f 100644 --- a/src/controllers/accounts/consent.js +++ b/src/controllers/accounts/consent.js @@ -19,7 +19,7 @@ consentController.get = async function (req, res, next) { const consented = await db.getObjectField('user:' + userData.uid, 'gdpr_consent'); userData.gdpr_consent = parseInt(consented, 10) === 1; userData.digest = { - frequency: meta.config.dailyDigestFreq, + frequency: meta.config.dailyDigestFreq || 'off', enabled: meta.config.dailyDigestFreq !== 'off', }; diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js index 6296683772..9e23d736fd 100644 --- a/src/controllers/accounts/edit.js +++ b/src/controllers/accounts/edit.js @@ -7,7 +7,6 @@ const helpers = require('../helpers'); const groups = require('../../groups'); const accountHelpers = require('./helpers'); const privileges = require('../../privileges'); -const file = require('../../file'); const editController = module.exports; @@ -125,46 +124,3 @@ async function getUserData(req) { userData.hasPassword = await user.hasPassword(userData.uid); return userData; } - -editController.uploadPicture = async function (req, res, next) { - const userPhoto = req.files.files[0]; - try { - const updateUid = await user.getUidByUserslug(req.params.userslug); - const isAllowed = await privileges.users.canEdit(req.uid, updateUid); - if (!isAllowed) { - return helpers.notAllowed(req, res); - } - await user.checkMinReputation(req.uid, updateUid, 'min:rep:profile-picture'); - const image = await user.uploadCroppedPicture({ - uid: updateUid, - file: userPhoto, - }); - res.json([{ - name: userPhoto.name, - url: image.url, - }]); - } catch (err) { - next(err); - } finally { - file.delete(userPhoto.path); - } -}; - -editController.uploadCoverPicture = async function (req, res, next) { - var params = JSON.parse(req.body.params); - var coverPhoto = req.files.files[0]; - try { - await user.checkMinReputation(req.uid, params.uid, 'min:rep:cover-picture'); - const image = await user.updateCoverPicture({ - file: coverPhoto, - uid: params.uid, - }); - res.json([{ - url: image.url, - }]); - } catch (err) { - next(err); - } finally { - file.delete(coverPhoto.path); - } -}; diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 7a646651ac..6686f17cfd 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -113,8 +113,8 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID) { userData['cover:position'] = validator.escape(String(userData['cover:position'] || '50% 50%')); userData['username:disableEdit'] = !userData.isAdmin && meta.config['username:disableEdit']; userData['email:disableEdit'] = !userData.isAdmin && meta.config['email:disableEdit']; - - return userData; + const hookData = await plugins.fireHook('filter:helpers.getUserDataByUserSlug', { userData: userData, callerUID: callerUID }); + return hookData.userData; }; async function getAllData(uid, callerUID) { @@ -143,6 +143,7 @@ async function getProfileMenu(uid, callerUID) { id: 'info', route: 'info', name: '[[user:account_info]]', + icon: 'fa-info', visibility: { self: false, other: false, @@ -155,6 +156,7 @@ async function getProfileMenu(uid, callerUID) { id: 'sessions', route: 'sessions', name: '[[pages:account/sessions]]', + icon: 'fa-group', visibility: { self: true, other: false, @@ -170,6 +172,7 @@ async function getProfileMenu(uid, callerUID) { id: 'consent', route: 'consent', name: '[[user:consent.title]]', + icon: 'fa-thumbs-o-up', visibility: { self: true, other: false, @@ -190,6 +193,8 @@ async function getProfileMenu(uid, callerUID) { async function parseAboutMe(userData) { if (!userData.aboutme) { + userData.aboutme = ''; + userData.aboutmeParsed = ''; return; } userData.aboutme = validator.escape(String(userData.aboutme || '')); diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index e0d7d2fc2c..34ba4cd7cc 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -106,12 +106,12 @@ settingsController.get = async function (req, res, next) { userData.categoryWatchState = { [userData.settings.categoryWatchState]: true }; - userData.disableCustomUserSkins = meta.config.disableCustomUserSkins; + userData.disableCustomUserSkins = meta.config.disableCustomUserSkins || 0; - userData.allowUserHomePage = meta.config.allowUserHomePage; + userData.allowUserHomePage = meta.config.allowUserHomePage || 1; - userData.hideFullname = meta.config.hideFullname; - userData.hideEmail = meta.config.hideEmail; + userData.hideFullname = meta.config.hideFullname || 0; + userData.hideEmail = meta.config.hideEmail || 0; userData.inTopicSearchAvailable = plugins.hasListeners('filter:topic.search'); diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index 4a93fb2089..4c1182e6f7 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -28,13 +28,20 @@ infoController.get = function (req, res) { } return 0; }); + + let port = nconf.get('port'); + if (!Array.isArray(port) && !isNaN(parseInt(port, 10))) { + port = [port]; + } + res.render('admin/development/info', { info: data, infoJSON: JSON.stringify(data, null, 4), host: os.hostname(), - port: nconf.get('port'), + port: port, nodeCount: data.length, timeout: timeoutMS, + ip: req.ip, }); }, timeoutMS); }; diff --git a/src/controllers/admin/navigation.js b/src/controllers/admin/navigation.js index 959b18cd9d..7dae04991c 100644 --- a/src/controllers/admin/navigation.js +++ b/src/controllers/admin/navigation.js @@ -28,7 +28,7 @@ navigationController.get = async function (req, res) { }); admin.available.forEach(function (available) { - available.groups = groups; + available.groups = admin.groups; }); admin.navigation = admin.enabled.slice(); diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index 41648e906d..a8762a3500 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -146,7 +146,7 @@ uploadsController.uploadFavicon = async function (req, res, next) { uploadsController.uploadTouchIcon = async function (req, res, next) { const uploadedFile = req.files.files[0]; const allowedTypes = ['image/png']; - const sizes = [36, 48, 72, 96, 144, 192]; + const sizes = [36, 48, 72, 96, 144, 192, 512]; if (validateUpload(res, uploadedFile, allowedTypes)) { try { diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index bb3768169f..29eb6603c9 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -195,6 +195,7 @@ authenticationController.registerComplete = function (req, res, next) { authenticationController.registerAbort = function (req, res) { // End the session and redirect to home req.session.destroy(function () { + res.clearCookie(nconf.get('sessionKey'), meta.configs.cookie.get()); res.redirect(nconf.get('relative_path') + '/'); }); }; @@ -423,9 +424,7 @@ authenticationController.logout = async function (req, res, next) { req.logout(); await destroyAsync(req); - res.clearCookie(nconf.get('sessionKey'), { - path: nconf.get('relative_path'), - }); + res.clearCookie(nconf.get('sessionKey'), meta.configs.cookie.get()); req.uid = 0; req.headers['x-csrf-token'] = req.csrfToken(); diff --git a/src/controllers/category.js b/src/controllers/category.js index 65eb23fb62..4edc7324d6 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -103,7 +103,7 @@ categoryController.get = async function (req, res, next) { addTags(categoryData, res); - categoryData['feeds:disableRSS'] = meta.config['feeds:disableRSS']; + categoryData['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0; categoryData['reputation:disabled'] = meta.config['reputation:disabled']; pageCount = Math.max(1, Math.ceil(categoryData.topic_count / userSettings.topicsPerPage)); categoryData.pagination = pagination.create(currentPage, pageCount, req.query); @@ -122,6 +122,7 @@ async function buildBreadcrumbs(req, categoryData) { { text: categoryData.name, url: nconf.get('relative_path') + '/category/' + categoryData.slug, + cid: categoryData.cid, }, ]; const crumbs = await helpers.buildCategoryBreadcrumbs(categoryData.parentCid); diff --git a/src/controllers/errors.js b/src/controllers/errors.js index 032924acd6..0000b36b9b 100644 --- a/src/controllers/errors.js +++ b/src/controllers/errors.js @@ -53,7 +53,7 @@ exports.handleErrors = function handleErrors(err, req, res, next) { // eslint-di return res.locals.isAPI ? res.set('X-Redirect', err.path).status(200).json(err.path) : res.redirect(nconf.get('relative_path') + err.path); } - winston.error(req.path + '\n', err); + winston.error(req.path + '\n' + err.stack); res.status(status || 500); diff --git a/src/controllers/groups.js b/src/controllers/groups.js index d618cbf858..14076783ca 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -1,6 +1,7 @@ 'use strict'; const validator = require('validator'); +const nconf = require('nconf'); const meta = require('../meta'); const groups = require('../groups'); @@ -29,18 +30,28 @@ groupsController.list = async function (req, res) { }; groupsController.details = async function (req, res, next) { + const lowercaseSlug = req.params.slug.toLowerCase(); + if (req.params.slug !== lowercaseSlug) { + if (res.locals.isAPI) { + req.params.slug = lowercaseSlug; + } else { + return res.redirect(nconf.get('relative_path') + '/groups/' + lowercaseSlug); + } + } const groupName = await groups.getGroupNameByGroupSlug(req.params.slug); if (!groupName) { return next(); } - const [exists, isHidden] = await Promise.all([ + const [exists, isHidden, isAdmin, isGlobalMod] = await Promise.all([ groups.exists(groupName), groups.isHidden(groupName), + user.isAdministrator(req.uid), + user.isGlobalModerator(req.uid), ]); if (!exists) { return next(); } - if (isHidden) { + if (isHidden && !isAdmin && !isGlobalMod) { const [isMember, isInvited] = await Promise.all([ groups.isMember(req.uid, groupName), groups.isInvited(req.uid, groupName), @@ -49,15 +60,13 @@ groupsController.details = async function (req, res, next) { return next(); } } - const [groupData, posts, isAdmin, isGlobalMod] = await Promise.all([ + const [groupData, posts] = await Promise.all([ groups.get(groupName, { uid: req.uid, truncateUserList: true, userListCount: 20, }), groups.getLatestMemberPosts(groupName, 10, req.uid), - user.isAdministrator(req.uid), - user.isGlobalModerator(req.uid), ]); if (!groupData) { return next(); @@ -109,21 +118,3 @@ groupsController.members = async function (req, res, next) { breadcrumbs: breadcrumbs, }); }; - -groupsController.uploadCover = async function (req, res, next) { - const params = JSON.parse(req.body.params); - - try { - const isOwner = await groups.ownership.isOwner(req.uid, params.groupName); - if (!isOwner) { - throw new Error('[[error:no-privileges]]'); - } - const image = await groups.updateCover(req.uid, { - file: req.files.files[0], - groupName: params.groupName, - }); - res.json([{ url: image.url }]); - } catch (err) { - next(err); - } -}; diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 4240ecb637..11a677116d 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -159,6 +159,7 @@ helpers.buildCategoryBreadcrumbs = async function (cid) { breadcrumbs.unshift({ text: String(data.name), url: nconf.get('relative_path') + '/category/' + data.slug, + cid: cid, }); } cid = data.parentCid; diff --git a/src/controllers/index.js b/src/controllers/index.js index 55c245b751..75c3fa0f3d 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -247,9 +247,12 @@ Controllers.robots = function (req, res) { Controllers.manifest = function (req, res, next) { var manifest = { name: meta.config.title || 'NodeBB', + short_name: meta.config['title:short'] || meta.config.title || 'NodeBB', start_url: nconf.get('relative_path') + '/', display: 'standalone', orientation: 'portrait', + theme_color: meta.config.themeColor || '#ffffff', + background_color: meta.config.backgroundColor || '#ffffff', icons: [], }; @@ -284,6 +287,11 @@ Controllers.manifest = function (req, res, next) { sizes: '192x192', type: 'image/png', density: 4.0, + }, { + src: nconf.get('relative_path') + '/assets/uploads/system/touchicon-512.png', + sizes: '512x512', + type: 'image/png', + density: 10.0, }); } plugins.fireHook('filter:manifest.build', { req: req, res: res, manifest: manifest }, function (err, data) { diff --git a/src/controllers/mods.js b/src/controllers/mods.js index ddcd695ccc..d3f216322b 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -66,7 +66,10 @@ modsController.flags.list = async function (req, res, next) { } // Pagination doesn't count as a filter - if (Object.keys(filters).length === 2 && filters.hasOwnProperty('page') && filters.hasOwnProperty('perPage')) { + if ( + (Object.keys(filters).length === 1 && filters.hasOwnProperty('page')) || + (Object.keys(filters).length === 2 && filters.hasOwnProperty('page') && filters.hasOwnProperty('perPage')) + ) { hasFilter = false; } @@ -130,6 +133,7 @@ modsController.flags.detail = async function (req, res, next) { }, {}), title: '[[pages:flag-details, ' + req.params.flagId + ']]', categories: results.categories, + filters: req.session.flags_filters || [], })); }; diff --git a/src/controllers/recent.js b/src/controllers/recent.js index 78e98b4d0e..13ee9b956c 100644 --- a/src/controllers/recent.js +++ b/src/controllers/recent.js @@ -62,9 +62,9 @@ recentController.getData = async function (req, url, sort) { data.canPost = canPost; data.categories = categoryData.categories; data.allCategoriesUrl = url + helpers.buildQueryString('', filter, ''); - data.selectedCategory = categoryData.selectedCategory; + data.selectedCategory = categoryData.selectedCategory || null; data.selectedCids = categoryData.selectedCids; - data['feeds:disableRSS'] = meta.config['feeds:disableRSS']; + data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0; data.rssFeedUrl = nconf.get('relative_path') + '/' + url + '.rss'; if (req.loggedIn) { data.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken; diff --git a/src/controllers/tags.js b/src/controllers/tags.js index 4360d562c2..da9c8122b9 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -32,10 +32,6 @@ tagsController.getTag = async function (req, res) { helpers.getCategoriesByStates(req.uid, '', states), ]); - if (Array.isArray(tids) && !tids.length) { - return res.render('tag', templateData); - } - templateData.categories = categoriesData.categories; templateData.topics = await topics.getTopics(tids, req.uid); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 06957d1fae..33f048b853 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -70,16 +70,12 @@ topicsController.get = async function getTopic(req, res, callback) { topics.modifyPostsByPrivilege(topicData, userPrivileges); const hookData = await plugins.fireHook('filter:controllers.topic.get', { topicData: topicData, uid: req.uid }); - await Promise.all([ - buildBreadcrumbs(hookData.topicData), - addTags(topicData, req, res), - ]); topicData.privileges = userPrivileges; topicData.topicStaleDays = meta.config.topicStaleDays; topicData['reputation:disabled'] = meta.config['reputation:disabled']; topicData['downvote:disabled'] = meta.config['downvote:disabled']; - topicData['feeds:disableRSS'] = meta.config['feeds:disableRSS']; + topicData['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0; topicData.bookmarkThreshold = meta.config.bookmarkThreshold; topicData.necroThreshold = meta.config.necroThreshold; topicData.postEditDuration = meta.config.postEditDuration; @@ -93,6 +89,12 @@ topicsController.get = async function getTopic(req, res, callback) { } topicData.postIndex = postIndex; + + await Promise.all([ + buildBreadcrumbs(hookData.topicData), + addTags(topicData, req, res), + ]); + topicData.pagination = pagination.create(currentPage, pageCount, req.query); topicData.pagination.rel.forEach(function (rel) { rel.href = nconf.get('url') + '/topic/' + topicData.slug + rel.href; @@ -156,6 +158,7 @@ async function buildBreadcrumbs(topicData) { { text: topicData.category.name, url: nconf.get('relative_path') + '/category/' + topicData.category.slug, + cid: topicData.category.cid, }, { text: topicData.title, @@ -337,5 +340,5 @@ topicsController.pagination = async function (req, res, callback) { rel.href = nconf.get('url') + '/topic/' + topic.slug + rel.href; }); - res.json(paginationData); + res.json({ pagination: paginationData }); }; diff --git a/src/controllers/user.js b/src/controllers/user.js index 69d52ffddf..861a88485a 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -178,7 +178,7 @@ userController.exportUploads = function (req, res, next) { }); archive.pipe(output); - winston.info('[user/export/uploads] Collating uploads for uid ' + targetUid); + winston.verbose('[user/export/uploads] Collating uploads for uid ' + targetUid); user.collateUploads(targetUid, archive, function (err) { if (err) { return next(err); diff --git a/src/controllers/users.js b/src/controllers/users.js index e6cbffaaf8..1818c53ca7 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -186,7 +186,7 @@ async function render(req, res, data) { data.adminInviteOnly = registrationType === 'admin-invite-only'; data.invites = await user.getInvitesNumber(req.uid); data.showInviteButton = req.loggedIn && ( - (registrationType === 'invite-only' && (!data.maximumInvites || data.invites < data.maximumInvites)) || + (registrationType === 'invite-only' && (data.isAdmin || !data.maximumInvites || data.invites < data.maximumInvites)) || (registrationType === 'admin-invite-only' && data.isAdmin) ); data['reputation:disabled'] = meta.config['reputation:disabled']; diff --git a/src/database/mongo/connection.js b/src/database/mongo/connection.js index cd380bddef..1001c58c03 100644 --- a/src/database/mongo/connection.js +++ b/src/database/mongo/connection.js @@ -45,9 +45,6 @@ connection.getConnectionOptions = function (mongo) { mongo = mongo || nconf.get('mongo'); var connOptions = { poolSize: 10, - reconnectTries: 3600, - reconnectInterval: 1000, - autoReconnect: true, connectTimeoutMS: 90000, useNewUrlParser: true, useUnifiedTopology: true, diff --git a/src/database/mongo/pubsub.js b/src/database/mongo/pubsub.js index d29065a68d..01981fbc3a 100644 --- a/src/database/mongo/pubsub.js +++ b/src/database/mongo/pubsub.js @@ -1,6 +1,6 @@ 'use strict'; -const mubsub = require('mubsub-nbb'); +const mubsub = require('@nodebb/mubsub'); const connection = require('./connection'); const client = mubsub(connection.getConnectionString(), connection.getConnectionOptions()); diff --git a/src/database/redis.js b/src/database/redis.js index 67ccc8088d..c235712ef0 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -43,7 +43,6 @@ redisModule.init = function (callback) { winston.error('NodeBB could not connect to your Redis database. Redis returned the following error', err); return callback(err); } - require('./redis/promisify')(redisModule.client); callback(); diff --git a/src/flags.js b/src/flags.js index 6f698f6511..d65cb44955 100644 --- a/src/flags.js +++ b/src/flags.js @@ -100,6 +100,7 @@ Flags.get = async function (flagId) { const flagObj = { state: 'open', + assignee: null, ...base, description: validator.escape(base.description), datetimeISO: utils.toISOString(base.datetime), @@ -164,6 +165,7 @@ Flags.list = async function (filters, uid) { const userObj = await user.getUserFields(flagObj.uid, ['username', 'picture']); flagObj = { state: 'open', + assignee: null, ...flagObj, reporter: { username: userObj.username, @@ -294,8 +296,7 @@ Flags.create = async function (type, id, uid, reason, timestamp) { if (type === 'post') { await db.sortedSetAdd('flags:byPid:' + id, timestamp, flagId); // by target pid if (targetUid) { - await db.sortedSetIncrBy('users:flags', 1, targetUid); - await user.incrementUserFieldBy(targetUid, 'flags', 1); + await user.incrementUserFlagsBy(targetUid, 1); } } @@ -361,7 +362,7 @@ Flags.getTargetCid = async function (type, id) { }; Flags.update = async function (flagId, uid, changeset) { - const current = await db.getObjectFields('flag:' + flagId, ['state', 'assignee', 'type', 'targetId']); + const current = await db.getObjectFields('flag:' + flagId, ['uid', 'state', 'assignee', 'type', 'targetId']); const now = changeset.datetime || Date.now(); const notifyAssignee = async function (assigneeId) { if (assigneeId === '' || parseInt(uid, 10) === parseInt(assigneeId, 10)) { @@ -402,6 +403,9 @@ Flags.update = async function (flagId, uid, changeset) { } else { tasks.push(db.sortedSetAdd('flags:byState:' + changeset[prop], now, flagId)); tasks.push(db.sortedSetRemove('flags:byState:' + current[prop], flagId)); + if (changeset[prop] === 'resolved' || changeset[prop] === 'rejected') { + tasks.push(notifications.rescind('flag:' + current.type + ':' + current.targetId + ':uid:' + current.uid)); + } } } else if (prop === 'assignee') { /* eslint-disable-next-line */ diff --git a/src/groups/delete.js b/src/groups/delete.js index 573ea389aa..ecdb50469c 100644 --- a/src/groups/delete.js +++ b/src/groups/delete.js @@ -2,7 +2,7 @@ const plugins = require('../plugins'); const utils = require('../utils'); -const db = require('./../database'); +const db = require('../database'); const batch = require('../batch'); module.exports = function (Groups) { diff --git a/src/groups/index.js b/src/groups/index.js index 00abdf94df..231b876c1c 100644 --- a/src/groups/index.js +++ b/src/groups/index.js @@ -145,18 +145,42 @@ Groups.getOwners = async function (groupName) { Groups.getOwnersAndMembers = async function (groupName, uid, start, stop) { const ownerUids = await db.getSetMembers('group:' + groupName + ':owners'); - const [owners, members] = await Promise.all([ - user.getUsers(ownerUids, uid), - user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop), - ]); + const countToReturn = stop - start + 1; + const ownerUidsOnPage = ownerUids.slice(start, stop !== -1 ? stop + 1 : undefined); + const owners = await user.getUsers(ownerUidsOnPage, uid); owners.forEach(function (user) { if (user) { user.isOwner = true; } }); - const nonOwners = members.filter(user => user && user.uid && !ownerUids.includes(user.uid.toString())); - return owners.concat(nonOwners); + let done = false; + let returnUsers = owners; + let memberStart = start - ownerUids.length; + let memberStop = memberStart + countToReturn - 1; + memberStart = Math.max(0, memberStart); + memberStop = Math.max(0, memberStop); + async function addMembers(start, stop) { + let batch = await user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop); + if (!batch.length) { + done = true; + } + batch = batch.filter(user => user && user.uid && !ownerUids.includes(user.uid.toString())); + returnUsers = returnUsers.concat(batch); + } + + if (stop === -1) { + await addMembers(memberStart, -1); + } else { + while (returnUsers.length < countToReturn && !done) { + /* eslint-disable no-await-in-loop */ + await addMembers(memberStart, memberStop); + memberStart = memberStop + 1; + memberStop = memberStart + countToReturn - 1; + } + } + + return countToReturn > 0 ? returnUsers.slice(0, countToReturn) : returnUsers; }; Groups.getByGroupslug = async function (slug, options) { diff --git a/src/groups/search.js b/src/groups/search.js index a19c6ea253..7247efbd4d 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -1,7 +1,7 @@ 'use strict'; const user = require('../user'); -const db = require('./../database'); +const db = require('../database'); module.exports = function (Groups) { Groups.search = async function (query, options) { diff --git a/src/groups/update.js b/src/groups/update.js index c7c0996557..7c5a3720e8 100644 --- a/src/groups/update.js +++ b/src/groups/update.js @@ -128,7 +128,6 @@ module.exports = function (Groups) { } async function checkNameChange(currentName, newName) { - Groups.validateGroupName(newName); if (Groups.isPrivilegeGroup(newName)) { throw new Error('[[error:invalid-group-name]]'); } @@ -137,6 +136,7 @@ module.exports = function (Groups) { if (currentName === newName || currentSlug === newSlug) { return; } + Groups.validateGroupName(newName); const [group, exists] = await Promise.all([ Groups.getGroupData(currentName), Groups.existsBySlug(newSlug), diff --git a/src/install.js b/src/install.js index 29a4e2bba1..0b5dd62d39 100644 --- a/src/install.js +++ b/src/install.js @@ -54,7 +54,9 @@ function checkSetupFlag(next) { if (nconf.get('setup')) { setupVal = JSON.parse(nconf.get('setup')); } - } catch (err) {} + } catch (err) { + winston.error('Invalid json in nconf.get(\'setup\'), ignoring setup values'); + } if (setupVal && typeof setupVal === 'object') { if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:password:confirm'] && setupVal['admin:email']) { diff --git a/src/messaging/data.js b/src/messaging/data.js index 21ea1e7d17..9fbf14eb63 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -82,6 +82,7 @@ module.exports = function (Messaging) { messages = await Promise.all(messages.map(async (message) => { if (message.system) { message.content = validator.escape(String(message.content)); + message.cleanedContent = utils.stripHTMLTags(utils.decodeHTMLEntities(message.content)); return message; } @@ -101,6 +102,8 @@ module.exports = function (Messaging) { } else if (index > 0 && message.fromuid !== messages[index - 1].fromuid) { // If the previous message was from the other person, this is also a new set message.newSet = true; + } else if (index === 0) { + message.newSet = true; } return message; diff --git a/src/messaging/delete.js b/src/messaging/delete.js index 9a4c551d2f..a48550db26 100644 --- a/src/messaging/delete.js +++ b/src/messaging/delete.js @@ -1,16 +1,33 @@ 'use strict'; -module.exports = function (Messaging) { - Messaging.deleteMessage = async mid => await doDeleteRestore(mid, 1); - Messaging.restoreMessage = async mid => await doDeleteRestore(mid, 0); +const sockets = require('../socket.io'); - async function doDeleteRestore(mid, state) { +module.exports = function (Messaging) { + Messaging.deleteMessage = async (mid, uid) => await doDeleteRestore(mid, 1, uid); + Messaging.restoreMessage = async (mid, uid) => await doDeleteRestore(mid, 0, uid); + + async function doDeleteRestore(mid, state, uid) { const field = state ? 'deleted' : 'restored'; - const cur = await Messaging.getMessageField(mid, 'deleted'); - if (cur === state) { + const { deleted, roomId } = await Messaging.getMessageFields(mid, ['deleted', 'roomId']); + if (deleted === state) { throw new Error('[[error:chat-' + field + '-already]]'); } await Messaging.setMessageField(mid, 'deleted', state); + + const [uids, messages] = await Promise.all([ + Messaging.getUidsInRoom(roomId, 0, -1), + Messaging.getMessagesData([mid], uid, roomId, true), + ]); + + uids.forEach(function (_uid) { + if (parseInt(_uid, 10) !== parseInt(uid, 10)) { + if (state === 1) { + sockets.in('uid_' + _uid).emit('event:chats.delete', mid); + } else if (state === 0) { + sockets.in('uid_' + _uid).emit('event:chats.restore', messages[0]); + } + } + }); } }; diff --git a/src/messaging/index.js b/src/messaging/index.js index a29abfe733..96887fd2b7 100644 --- a/src/messaging/index.js +++ b/src/messaging/index.js @@ -40,14 +40,13 @@ Messaging.getMessages = async (params) => { }); mids.reverse(); - let messageData = await Messaging.getMessagesData(mids, params.uid, params.roomId, isNew); + const messageData = await Messaging.getMessagesData(mids, params.uid, params.roomId, isNew); messageData.forEach(function (messageData) { messageData.index = indices[messageData.messageId.toString()]; - }); - - // Filter out deleted messages unless you're the sender of said message - messageData = messageData.filter(function (messageData) { - return (!messageData.deleted || messageData.fromuid === parseInt(params.uid, 10)); + messageData.isOwner = messageData.fromuid === parseInt(params.uid, 10); + if (messageData.deleted && !messageData.isOwner) { + messageData.content = '[[modules:chat.message-deleted]]'; + } }); return messageData; @@ -100,9 +99,7 @@ Messaging.getRecentChats = async (callerUid, uid, start, stop) => { unread: db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds), users: Promise.all(roomIds.map(async (roomId) => { let uids = await db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9); - uids = uids.filter(function (value) { - return value && parseInt(value, 10) !== parseInt(uid, 10); - }); + uids = uids.filter(_uid => _uid && parseInt(_uid, 10) !== parseInt(uid, 10)); return await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline']); })), teasers: Promise.all(roomIds.map(async roomId => Messaging.getTeaser(uid, roomId))), diff --git a/src/meta/build.js b/src/meta/build.js index f9370d1b9f..a6315edeeb 100644 --- a/src/meta/build.js +++ b/src/meta/build.js @@ -105,7 +105,7 @@ function beforeBuild(targets, callback) { db.init(next); }, function (next) { - meta = require('../meta'); + meta = require('./index'); meta.themes.setupPaths(next); }, function (next) { diff --git a/src/meta/configs.js b/src/meta/configs.js index 036dc7f74e..c28d3e2ff8 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -8,7 +8,7 @@ const util = require('util'); const db = require('../database'); const pubsub = require('../pubsub'); -const Meta = require('../meta'); +const Meta = require('./index'); const cacheBuster = require('./cacheBuster'); const defaults = require('../../install/data/defaults'); @@ -147,6 +147,27 @@ Configs.remove = async function (field) { await db.deleteObjectField('config', field); }; +Configs.cookie = { + get: () => { + const cookie = {}; + + if (nconf.get('cookieDomain') || Meta.config.cookieDomain) { + cookie.domain = nconf.get('cookieDomain') || Meta.config.cookieDomain; + } + + if (nconf.get('secure')) { + cookie.secure = true; + } + + var relativePath = nconf.get('relative_path'); + if (relativePath !== '') { + cookie.path = relativePath; + } + + return cookie; + }, +}; + async function processConfig(data) { ensurePositiveInteger(data, 'maximumUsernameLength'); ensurePositiveInteger(data, 'minimumUsernameLength'); diff --git a/src/meta/logs.js b/src/meta/logs.js index 72f478da75..5a6fe5c5ed 100644 --- a/src/meta/logs.js +++ b/src/meta/logs.js @@ -7,7 +7,7 @@ const readFileAsync = util.promisify(fs.readFile); const truncateAsync = util.promisify(fs.truncate); const Logs = module.exports; -Logs.path = path.join(__dirname, '..', '..', 'logs', 'output.log'); +Logs.path = path.resolve(__dirname, '../../logs/output.log'); Logs.get = async function () { return await readFileAsync(Logs.path, 'utf-8'); diff --git a/src/meta/settings.js b/src/meta/settings.js index 6e0b151801..a80c948899 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -1,73 +1,108 @@ 'use strict'; -var async = require('async'); +const db = require('../database'); +const plugins = require('../plugins'); +const Meta = require('./index'); +const pubsub = require('../pubsub'); -var db = require('../database'); -var plugins = require('../plugins'); -var Meta = require('../meta'); -var pubsub = require('../pubsub'); +const Settings = module.exports; -var Settings = module.exports; +Settings.get = async function (hash) { + const data = await db.getObject('settings:' + hash) || {}; + const sortedLists = await db.getSetMembers('settings:' + hash + ':sorted-lists'); -Settings.get = function (hash, callback) { - db.getObject('settings:' + hash, function (err, settings) { - callback(err, settings || {}); - }); + await Promise.all(sortedLists.map(async function (list) { + const members = await db.getSortedSetRange('settings:' + hash + ':sorted-list:' + list, 0, -1) || []; + const keys = []; + + data[list] = []; + for (const order of members) { + keys.push('settings:' + hash + ':sorted-list:' + list + ':' + order); + } + + const objects = await db.getObjects(keys); + objects.forEach(function (obj) { + data[list].push(obj); + }); + })); + + return data; }; -Settings.getOne = function (hash, field, callback) { - db.getObjectField('settings:' + hash, field, callback); +Settings.getOne = async function (hash, field) { + const data = await Settings.get(hash); + return data[field] !== undefined ? data[field] : null; }; -Settings.set = function (hash, values, quiet, callback) { - if (!callback && typeof quiet === 'function') { - callback = quiet; - quiet = false; - } else { - quiet = quiet || false; +Settings.set = async function (hash, values, quiet) { + quiet = quiet || false; + + const sortedLists = []; + + for (const key in values) { + if (values.hasOwnProperty(key)) { + if (Array.isArray(values[key]) && typeof values[key][0] !== 'string') { + sortedLists.push(key); + } + } } - async.waterfall([ - function (next) { - db.setObject('settings:' + hash, values, next); - }, - function (next) { - plugins.fireHook('action:settings.set', { - plugin: hash, - settings: values, + if (sortedLists.length) { + await db.delete('settings:' + hash + ':sorted-lists'); + await db.setAdd('settings:' + hash + ':sorted-lists', sortedLists); + + await Promise.all(sortedLists.map(async function (list) { + await db.delete('settings:' + hash + ':sorted-list:' + list); + await Promise.all(values[list].map(async function (data, order) { + await db.delete('settings:' + hash + ':sorted-list:' + list + ':' + order); + })); + })); + + const ops = []; + sortedLists.forEach(function (list) { + const arr = values[list]; + delete values[list]; + + arr.forEach(function (data, order) { + ops.push(db.sortedSetAdd('settings:' + hash + ':sorted-list:' + list, order, order)); + ops.push(db.setObject('settings:' + hash + ':sorted-list:' + list + ':' + order, data)); }); - pubsub.publish('action:settings.set.' + hash, values); - Meta.reloadRequired = !quiet; - next(); - }, - ], callback); + }); + + await Promise.all(ops); + } + + if (Object.keys(values).length) { + await db.setObject('settings:' + hash, values); + } + + plugins.fireHook('action:settings.set', { + plugin: hash, + settings: values, + }); + + pubsub.publish('action:settings.set.' + hash, values); + Meta.reloadRequired = !quiet; }; -Settings.setOne = function (hash, field, value, callback) { - var data = {}; +Settings.setOne = async function (hash, field, value) { + const data = {}; data[field] = value; - Settings.set(hash, data, callback); + await Settings.set(hash, data); }; -Settings.setOnEmpty = function (hash, values, callback) { - async.waterfall([ - function (next) { - db.getObject('settings:' + hash, next); - }, - function (settings, next) { - settings = settings || {}; - var empty = {}; - Object.keys(values).forEach(function (key) { - if (!settings.hasOwnProperty(key)) { - empty[key] = values[key]; - } - }); +Settings.setOnEmpty = async function (hash, values) { + const settings = await Settings.get(hash) || {}; + const empty = {}; - if (Object.keys(empty).length) { - Settings.set(hash, empty, next); - } else { - next(); - } - }, - ], callback); + Object.keys(values).forEach(function (key) { + if (!settings.hasOwnProperty(key)) { + empty[key] = values[key]; + } + }); + + + if (Object.keys(empty).length) { + await Settings.set(hash, empty); + } }; diff --git a/src/meta/sounds.js b/src/meta/sounds.js index 3c6fb6eff3..b185743db9 100644 --- a/src/meta/sounds.js +++ b/src/meta/sounds.js @@ -15,7 +15,7 @@ const writeFileAsync = util.promisify(fs.writeFile); const file = require('../file'); const plugins = require('../plugins'); const user = require('../user'); -const Meta = require('../meta'); +const Meta = require('./index'); const soundsPath = path.join(__dirname, '../../build/public/sounds'); const uploadsPath = path.join(__dirname, '../../public/uploads/sounds'); diff --git a/src/meta/tags.js b/src/meta/tags.js index d6a01889b9..4044326079 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -4,7 +4,7 @@ const nconf = require('nconf'); const winston = require('winston'); const plugins = require('../plugins'); -const Meta = require('../meta'); +const Meta = require('./index'); const utils = require('../utils'); const Tags = module.exports; diff --git a/src/meta/themes.js b/src/meta/themes.js index 1499c83c8b..1450c46365 100644 --- a/src/meta/themes.js +++ b/src/meta/themes.js @@ -14,7 +14,7 @@ const fsReadfile = util.promisify(fs.readFile); const file = require('../file'); const db = require('../database'); -const Meta = require('../meta'); +const Meta = require('./index'); const events = require('../events'); const utils = require('../../public/src/utils'); diff --git a/src/middleware/headers.js b/src/middleware/headers.js index 8be72ee9c7..453c75736f 100644 --- a/src/middleware/headers.js +++ b/src/middleware/headers.js @@ -3,6 +3,7 @@ var os = require('os'); var winston = require('winston'); var _ = require('lodash'); +const nconf = require('nconf'); var meta = require('../meta'); var languages = require('../languages'); @@ -54,6 +55,12 @@ module.exports = function (middleware) { headers['X-Upstream-Hostname'] = os.hostname(); } + // Ensure that the session is valid. This block guards against edge-cases where the server-side session has + // been deleted (but client-side cookie still exists) + if (req.uid > 0 && !req.session.meta && !res.get('Set-Cookie')) { + res.clearCookie(nconf.get('sessionKey'), meta.configs.cookie.get()); + } + for (var key in headers) { if (headers.hasOwnProperty(key) && headers[key]) { res.setHeader(key, headers[key]); diff --git a/src/middleware/index.js b/src/middleware/index.js index 9722c42690..9af5605885 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -17,7 +17,7 @@ var analytics = require('../analytics'); var privileges = require('../privileges'); var controllers = { - api: require('./../controllers/api'), + api: require('../controllers/api'), helpers: require('../controllers/helpers'), }; @@ -82,13 +82,24 @@ middleware.pageView = function pageView(req, res, next) { }; -middleware.pluginHooks = function pluginHooks(req, res, next) { - async.each(plugins.loadedHooks['filter:router.page'] || [], function (hookObj, next) { - hookObj.method(req, res, next); - }, function (err) { - // If it got here, then none of the subscribed hooks did anything, or there were no hooks - next(err); - }); +middleware.pluginHooks = async function pluginHooks(req, res, next) { + // TODO: Deprecate in v2.0 + try { + await async.each(plugins.loadedHooks['filter:router.page'] || [], function (hookObj, next) { + hookObj.method(req, res, next); + }); + + await plugins.fireHook('response:router.page', { + req: req, + res: res, + }); + } catch (err) { + return next(err); + } + + if (!res.headersSent) { + next(); + } }; middleware.validateFiles = function validateFiles(req, res, next) { diff --git a/src/middleware/maintenance.js b/src/middleware/maintenance.js index 00b34d4be3..35f28f4b57 100644 --- a/src/middleware/maintenance.js +++ b/src/middleware/maintenance.js @@ -11,6 +11,9 @@ module.exports = function (middleware) { return setImmediate(next); } + const hooksAsync = util.promisify(middleware.pluginHooks); + await hooksAsync(req, res); + const url = req.url.replace(nconf.get('relative_path'), ''); if (url.startsWith('/login') || url.startsWith('/api/login')) { return setImmediate(next); diff --git a/src/middleware/render.js b/src/middleware/render.js index 23579bb70f..a6257d04a8 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -127,6 +127,13 @@ module.exports = function (middleware) { parts.push('page-topic-category-' + templateData.category.cid); parts.push('page-topic-category-' + utils.slugify(templateData.category.name)); } + if (templateData.breadcrumbs) { + templateData.breadcrumbs.forEach(function (crumb) { + if (crumb.hasOwnProperty('cid')) { + parts.push('parent-category-' + crumb.cid); + } + }); + } parts.push('page-status-' + res.statusCode); return parts.join(' '); diff --git a/src/middleware/user.js b/src/middleware/user.js index 9e794a6a9e..e2d88f84d7 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -16,31 +16,26 @@ const controllers = { }; module.exports = function (middleware) { - function authenticate(req, res, next, callback) { + async function authenticate(req, res, next, callback) { if (req.loggedIn) { return next(); } - if (plugins.hasListeners('response:middleware.authenticate')) { - return plugins.fireHook('response:middleware.authenticate', { - req: req, - res: res, - next: function (err) { - if (err) { - return next(err); - } - auth.setAuthVars(req, res, function () { - if (req.loggedIn && req.user && req.user.uid) { - return next(); - } + await plugins.fireHook('response:middleware.authenticate', { + req: req, + res: res, + next: function () {}, // no-op for backwards compatibility + }); - callback(); - }); - }, + if (!res.headersSent) { + auth.setAuthVars(req, res, function () { + if (req.loggedIn && req.user && req.user.uid) { + return next(); + } + + callback(); }); } - - callback(); } middleware.authenticate = function middlewareAuthenticate(req, res, next) { @@ -78,6 +73,8 @@ module.exports = function (middleware) { if (!allowed) { return controllers.helpers.notAllowed(req, res); } + + return next(); } middleware.checkGlobalPrivacySettings = function checkGlobalPrivacySettings(req, res, next) { diff --git a/src/navigation/admin.js b/src/navigation/admin.js index 33a96fc193..5398ac8375 100644 --- a/src/navigation/admin.js +++ b/src/navigation/admin.js @@ -60,6 +60,9 @@ admin.get = async function () { async function getAvailable() { const core = require('../../install/data/navigation.json').map(function (item) { item.core = true; + item.id = item.id || ''; + item.properties = item.properties || { targetBlank: false }; + return item; }); diff --git a/src/plugins/install.js b/src/plugins/install.js index eebb384b90..dace50b10e 100644 --- a/src/plugins/install.js +++ b/src/plugins/install.js @@ -14,7 +14,8 @@ const pubsub = require('../pubsub'); const statAsync = util.promisify(fs.stat); -const packageManager = nconf.get('package_manager') === 'yarn' ? 'yarn' : 'npm'; +const supportedPackageManagerList = require('../cli/package-install').supportedPackageManager; // load config from src/cli/package-install.js +const packageManager = supportedPackageManagerList.indexOf(nconf.get('package_manager')) >= 0 ? nconf.get('package_manager') : 'npm'; let packageManagerExecutable = packageManager; const packageManagerCommands = { yarn: { @@ -25,6 +26,14 @@ const packageManagerCommands = { install: 'install', uninstall: 'uninstall', }, + cnpm: { + install: 'install', + uninstall: 'uninstall', + }, + pnpm: { + install: 'install', + uninstall: 'uninstall', + }, }; if (process.platform === 'win32') { diff --git a/src/posts/summary.js b/src/posts/summary.js index c86d10cdfa..66ee954b8f 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -74,7 +74,7 @@ module.exports = function (Posts) { } async function getTopicAndCategories(tids) { - const topicsData = await topics.getTopicsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'postcount', 'mainPid']); + const topicsData = await topics.getTopicsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'postcount', 'mainPid', 'teaserPid']); const cids = _.uniq(topicsData.map(topic => topic && topic.cid)); const categoriesData = await categories.getCategoriesFields(cids, ['cid', 'name', 'icon', 'slug', 'parentCid', 'bgColor', 'color', 'image', 'imageClass']); return { topics: topicsData, categories: categoriesData }; diff --git a/src/posts/user.js b/src/posts/user.js index 49706ec59b..25e5183996 100644 --- a/src/posts/user.js +++ b/src/posts/user.js @@ -1,16 +1,16 @@ 'use strict'; -var async = require('async'); -var validator = require('validator'); -var _ = require('lodash'); +const async = require('async'); +const validator = require('validator'); +const _ = require('lodash'); const db = require('../database'); -var user = require('../user'); +const user = require('../user'); const topics = require('../topics'); -var groups = require('../groups'); -var meta = require('../meta'); -var plugins = require('../plugins'); -var privileges = require('../privileges'); +const groups = require('../groups'); +const meta = require('../meta'); +const plugins = require('../plugins'); +const privileges = require('../privileges'); module.exports = function (Posts) { Posts.getUserInfoForPosts = async function (uids, uid) { @@ -158,7 +158,7 @@ module.exports = function (Posts) { db.sortedSetRemoveBulk(bulkRemove), db.sortedSetAddBulk(bulkAdd), user.incrementUserPostCountBy(toUid, pids.length), - updateReputation(toUid, repChange), + user.incrementUserReputationBy(toUid, repChange), handleMainPidOwnerChange(postData, toUid), reduceCounters(postsByUser), updateTopicPosters(postData, toUid), @@ -171,7 +171,7 @@ module.exports = function (Posts) { const repChange = posts.reduce((acc, val) => acc + val.votes, 0); await Promise.all([ user.incrementUserPostCountBy(uid, -posts.length), - updateReputation(uid, -repChange), + user.incrementUserReputationBy(uid, -repChange), ]); }); } @@ -187,14 +187,6 @@ module.exports = function (Posts) { }); } - async function updateReputation(uid, change) { - if (!change) { - return; - } - const newReputation = await user.incrementUserFieldBy(uid, 'reputation', change); - await db.sortedSetAdd('users:reputation', newReputation, uid); - } - async function handleMainPidOwnerChange(postData, toUid) { const tids = _.uniq(postData.map(p => p.tid)); const topicData = await topics.getTopicsFields(tids, ['mainPid', 'timestamp']); @@ -230,7 +222,10 @@ module.exports = function (Posts) { async function reduceTopicCounts(postsByUser) { await async.eachSeries(Object.keys(postsByUser), async function (uid) { const posts = postsByUser[uid]; - await user.incrementUserFieldBy(uid, 'topiccount', -posts.length); + const exists = await user.exists(uid); + if (exists) { + await user.incrementUserFieldBy(uid, 'topiccount', -posts.length); + } }); } }; diff --git a/src/posts/votes.js b/src/posts/votes.js index 8e930cc8d0..92ed268d4c 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -1,14 +1,14 @@ 'use strict'; -var meta = require('../meta'); -var db = require('../database'); -var user = require('../user'); -var topics = require('../topics'); -var plugins = require('../plugins'); -var privileges = require('../privileges'); +const meta = require('../meta'); +const db = require('../database'); +const user = require('../user'); +const topics = require('../topics'); +const plugins = require('../plugins'); +const privileges = require('../privileges'); module.exports = function (Posts) { - var votesInProgress = {}; + const votesInProgress = {}; Posts.upvote = async function (pid, uid) { if (meta.config['reputation:disabled']) { @@ -25,8 +25,7 @@ module.exports = function (Posts) { putVoteInProgress(pid, uid); try { - const data = await toggleVote('upvote', pid, uid); - return data; + return await toggleVote('upvote', pid, uid); } finally { clearVoteProgress(pid, uid); } @@ -51,8 +50,7 @@ module.exports = function (Posts) { putVoteInProgress(pid, uid); try { - const data = toggleVote('downvote', pid, uid); - return data; + return await toggleVote('downvote', pid, uid); } finally { clearVoteProgress(pid, uid); } @@ -65,8 +63,7 @@ module.exports = function (Posts) { putVoteInProgress(pid, uid); try { - const data = await unvote(pid, uid, 'unvote'); - return data; + return await unvote(pid, uid, 'unvote'); } finally { clearVoteProgress(pid, uid); } @@ -85,13 +82,8 @@ module.exports = function (Posts) { const data = pids.map(() => false); return { upvotes: data, downvotes: data }; } - var upvoteSets = []; - var downvoteSets = []; - - for (var i = 0; i < pids.length; i += 1) { - upvoteSets.push('pid:' + pids[i] + ':upvote'); - downvoteSets.push('pid:' + pids[i] + ':downvote'); - } + const upvoteSets = pids.map(pid => 'pid:' + pid + ':upvote'); + const downvoteSets = pids.map(pid => 'pid:' + pid + ':downvote'); const data = await db.isMemberOfSets(upvoteSets.concat(downvoteSets), uid); return { upvotes: data.slice(0, pids.length), @@ -114,7 +106,7 @@ module.exports = function (Posts) { function clearVoteProgress(pid, uid) { if (Array.isArray(votesInProgress[uid])) { - var index = votesInProgress[uid].indexOf(parseInt(pid, 10)); + const index = votesInProgress[uid].indexOf(parseInt(pid, 10)); if (index !== -1) { votesInProgress[uid].splice(index, 1); } @@ -141,8 +133,8 @@ module.exports = function (Posts) { throw new Error('[[error:not-enough-reputation-to-downvote]]'); } - var hook; - var current = voteStatus.upvoted ? 'upvote' : 'downvote'; + let hook; + let current = voteStatus.upvoted ? 'upvote' : 'downvote'; if ((voteStatus.upvoted && command === 'downvote') || (voteStatus.downvoted && command === 'upvote')) { // e.g. User *has* upvoted, and clicks downvote hook = command; @@ -172,26 +164,22 @@ module.exports = function (Posts) { if (uid <= 0) { throw new Error('[[error:not-logged-in]]'); } - const postData = await Posts.getPostFields(pid, ['pid', 'uid', 'tid']); - - var now = Date.now(); + const now = Date.now(); if (type === 'upvote' && !unvote) { - db.sortedSetAdd('uid:' + uid + ':upvote', now, pid); + await db.sortedSetAdd('uid:' + uid + ':upvote', now, pid); } else { - db.sortedSetRemove('uid:' + uid + ':upvote', pid); + await db.sortedSetRemove('uid:' + uid + ':upvote', pid); } if (type === 'upvote' || unvote) { - db.sortedSetRemove('uid:' + uid + ':downvote', pid); + await db.sortedSetRemove('uid:' + uid + ':downvote', pid); } else { - db.sortedSetAdd('uid:' + uid + ':downvote', now, pid); + await db.sortedSetAdd('uid:' + uid + ':downvote', now, pid); } - const newReputation = await user[type === 'upvote' ? 'incrementUserFieldBy' : 'decrementUserFieldBy'](postData.uid, 'reputation', 1); - if (parseInt(postData.uid, 10)) { - db.sortedSetAdd('users:reputation', newReputation, postData.uid); - } + const postData = await Posts.getPostFields(pid, ['pid', 'uid', 'tid']); + const newReputation = await user.incrementUserReputationBy(postData.uid, type === 'upvote' ? 1 : -1); await adjustPostVotes(postData, uid, type, unvote); @@ -207,7 +195,7 @@ module.exports = function (Posts) { } async function adjustPostVotes(postData, uid, type, unvote) { - var notType = (type === 'upvote' ? 'downvote' : 'upvote'); + const notType = (type === 'upvote' ? 'downvote' : 'upvote'); if (unvote) { await db.setRemove('pid:' + postData.pid + ':' + type, uid); } else { @@ -237,6 +225,7 @@ module.exports = function (Posts) { downvotes: postData.downvotes, }), ]); + plugins.fireHook('action:post.updatePostVoteCount', { post: postData }); }; async function updateTopicVoteCount(postData) { diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index c332315873..47de313b79 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -16,11 +16,26 @@ const uidToSystemGroup = { '-1': 'spiders', }; +helpers.isUsersAllowedTo = async function (privilege, uids, cid) { + const [hasUserPrivilege, hasGroupPrivilege] = await Promise.all([ + groups.isMembers(uids, 'cid:' + cid + ':privileges:' + privilege), + groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:' + privilege), + ]); + const allowed = uids.map((uid, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]); + const result = await plugins.fireHook('filter:privileges:isUsersAllowedTo', { allowed: allowed, privilege: privilege, uids: uids, cid: cid }); + return result.allowed; +}; + helpers.isUserAllowedTo = async function (privilege, uid, cid) { + let allowed; if (Array.isArray(privilege) && !Array.isArray(cid)) { - return await isUserAllowedToPrivileges(privilege, uid, cid); + allowed = await isUserAllowedToPrivileges(privilege, uid, cid); } else if (Array.isArray(cid) && !Array.isArray(privilege)) { - return await isUserAllowedToCids(privilege, uid, cid); + allowed = await isUserAllowedToCids(privilege, uid, cid); + } + if (allowed) { + const result = await plugins.fireHook('filter:privileges:isUserAllowedTo', { allowed: allowed, privilege: privilege, uid: uid, cid: cid }); + return result.allowed; } throw new Error('[[error:invalid-data]]'); }; @@ -63,14 +78,6 @@ async function checkIfAllowed(uid, userKeys, groupKeys) { return userKeys.map((key, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]); } -helpers.isUsersAllowedTo = async function (privilege, uids, cid) { - const [hasUserPrivilege, hasGroupPrivilege] = await Promise.all([ - groups.isMembers(uids, 'cid:' + cid + ':privileges:' + privilege), - groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:' + privilege), - ]); - return uids.map((uid, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]); -}; - async function isSystemGroupAllowedToCids(privilege, uid, cids) { const groupKeys = cids.map(cid => 'cid:' + cid + ':privileges:groups:' + privilege); return await groups.isMemberOfGroups(uidToSystemGroup[uid], groupKeys); diff --git a/src/routes/accounts.js b/src/routes/accounts.js index dfc975927a..0f44ab6938 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -14,12 +14,12 @@ module.exports = function (app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/following', middleware, middlewares, controllers.accounts.follow.getFollowing); setupPageRoute(app, '/user/:userslug/followers', middleware, middlewares, controllers.accounts.follow.getFollowers); - setupPageRoute(app, '/user/:userslug/categories', middleware, middlewares, controllers.accounts.categories.get); setupPageRoute(app, '/user/:userslug/posts', middleware, middlewares, controllers.accounts.posts.getPosts); setupPageRoute(app, '/user/:userslug/topics', middleware, middlewares, controllers.accounts.posts.getTopics); setupPageRoute(app, '/user/:userslug/best', middleware, middlewares, controllers.accounts.posts.getBestPosts); setupPageRoute(app, '/user/:userslug/groups', middleware, middlewares, controllers.accounts.groups.get); + setupPageRoute(app, '/user/:userslug/categories', middleware, accountMiddlewares, controllers.accounts.categories.get); setupPageRoute(app, '/user/:userslug/bookmarks', middleware, accountMiddlewares, controllers.accounts.posts.getBookmarks); setupPageRoute(app, '/user/:userslug/watched', middleware, accountMiddlewares, controllers.accounts.posts.getWatchedTopics); setupPageRoute(app, '/user/:userslug/ignored', middleware, accountMiddlewares, controllers.accounts.posts.getIgnoredTopics); diff --git a/src/routes/api.js b/src/routes/api.js index 76aad99fe8..7d71051e92 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -40,8 +40,4 @@ module.exports = function (app, middleware, controllers) { var middlewares = [middleware.maintenanceMode, multipartMiddleware, middleware.validateFiles, middleware.applyCSRF]; router.post('/post/upload', middlewares, uploadsController.uploadPost); router.post('/topic/thumb/upload', middlewares, uploadsController.uploadThumb); - router.post('/user/:userslug/uploadpicture', middlewares.concat([middleware.exposeUid, middleware.authenticate, middleware.canViewUsers, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadPicture); - - router.post('/user/:userslug/uploadcover', middlewares.concat([middleware.exposeUid, middleware.authenticate, middleware.canViewUsers, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadCoverPicture); - router.post('/groups/uploadpicture', middlewares.concat([middleware.authenticate]), controllers.groups.uploadCover); }; diff --git a/src/routes/debug.js b/src/routes/debug.js index 460534fcdc..b55c1f2440 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -3,6 +3,9 @@ var express = require('express'); var nconf = require('nconf'); +const fs = require('fs').promises; +const path = require('path'); + module.exports = function (app) { var router = express.Router(); @@ -10,5 +13,17 @@ module.exports = function (app) { res.redirect(404); }); + // Redoc + router.get('/spec/read', async (req, res) => { + const handle = await fs.open(path.resolve(__dirname, '../../public/vendor/redoc/index.html'), 'r'); + let html = await handle.readFile({ + encoding: 'utf-8', + }); + await handle.close(); + + html = html.replace('apiUrl', nconf.get('relative_path') + '/assets/openapi/read.yaml'); + res.status(200).type('text/html').send(html); + }); + app.use(nconf.get('relative_path') + '/debug', router); }; diff --git a/src/sitemap.js b/src/sitemap.js index 880a855aee..42adfb6870 100644 --- a/src/sitemap.js +++ b/src/sitemap.js @@ -41,15 +41,15 @@ sitemap.getPages = async function () { changefreq: 'weekly', priority: 0.6, }, { - url: '/recent', + url: `${nconf.get('relative_path')}/recent`, changefreq: 'daily', priority: 0.4, }, { - url: '/users', + url: `${nconf.get('relative_path')}/users`, changefreq: 'daily', priority: 0.4, }, { - url: '/groups', + url: `${nconf.get('relative_path')}/groups`, changefreq: 'daily', priority: 0.4, }]; @@ -75,7 +75,7 @@ sitemap.getCategories = async function () { categoriesData.forEach(function (category) { if (category) { categoryUrls.push({ - url: '/category/' + category.slug, + url: `${nconf.get('relative_path')}/category/` + category.slug, changefreq: 'weekly', priority: 0.4, }); @@ -112,7 +112,7 @@ sitemap.getTopicPage = async function (page) { topicData.forEach(function (topic) { if (topic) { topicUrls.push({ - url: '/topic/' + topic.slug, + url: `${nconf.get('relative_path')}/topic/` + topic.slug, lastmodISO: utils.toISOString(topic.lastposttime), changefreq: 'daily', priority: 0.6, diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 4c6a748be8..2c06be484c 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -1,48 +1,36 @@ 'use strict'; -const async = require('async'); const winston = require('winston'); -const fs = require('fs'); -const path = require('path'); -const nconf = require('nconf'); const meta = require('../meta'); -const plugins = require('../plugins'); -const widgets = require('../widgets'); const user = require('../user'); -const userDigest = require('../user/digest'); -const userEmail = require('../user/email'); -const logger = require('../logger'); const events = require('../events'); -const notifications = require('../notifications'); -const emailer = require('../emailer'); const db = require('../database'); -const analytics = require('../analytics'); -const websockets = require('../socket.io/index'); +const websockets = require('./index'); const index = require('./index'); const getAdminSearchDict = require('../admin/search').getDictionary; -const utils = require('../../public/src/utils'); const SocketAdmin = module.exports; SocketAdmin.user = require('./admin/user'); SocketAdmin.categories = require('./admin/categories'); +SocketAdmin.settings = require('./admin/settings'); SocketAdmin.groups = require('./admin/groups'); SocketAdmin.tags = require('./admin/tags'); SocketAdmin.rewards = require('./admin/rewards'); SocketAdmin.navigation = require('./admin/navigation'); SocketAdmin.rooms = require('./admin/rooms'); SocketAdmin.social = require('./admin/social'); -SocketAdmin.themes = {}; -SocketAdmin.plugins = {}; -SocketAdmin.widgets = {}; -SocketAdmin.config = {}; -SocketAdmin.settings = {}; -SocketAdmin.email = {}; -SocketAdmin.analytics = {}; -SocketAdmin.logs = {}; -SocketAdmin.errors = {}; -SocketAdmin.uploads = {}; -SocketAdmin.digest = {}; +SocketAdmin.themes = require('./admin/themes'); +SocketAdmin.plugins = require('./admin/plugins'); +SocketAdmin.widgets = require('./admin/widgets'); +SocketAdmin.config = require('./admin/config'); +SocketAdmin.settings = require('./admin/settings'); +SocketAdmin.email = require('./admin/email'); +SocketAdmin.analytics = require('./admin/analytics'); +SocketAdmin.logs = require('./admin/logs'); +SocketAdmin.errors = require('./admin/errors'); +SocketAdmin.uploads = require('./admin/uploads'); +SocketAdmin.digest = require('./admin/digest'); SocketAdmin.before = async function (socket, method) { const isAdmin = await user.isAdministrator(socket.uid); @@ -89,246 +77,6 @@ SocketAdmin.fireEvent = function (socket, data, callback) { callback(); }; -SocketAdmin.themes.getInstalled = function (socket, data, callback) { - meta.themes.get(callback); -}; - -SocketAdmin.themes.set = async function (socket, data) { - if (!data) { - throw new Error('[[error:invalid-data]]'); - } - if (data.type === 'local') { - await widgets.reset(); - } - - data.ip = socket.ip; - data.uid = socket.uid; - - await meta.themes.set(data); -}; - -SocketAdmin.plugins.toggleActive = async function (socket, plugin_id) { - require('../posts/cache').reset(); - const data = await plugins.toggleActive(plugin_id); - await events.log({ - type: 'plugin-' + (data.active ? 'activate' : 'deactivate'), - text: plugin_id, - uid: socket.uid, - }); - return data; -}; - -SocketAdmin.plugins.toggleInstall = async function (socket, data) { - require('../posts/cache').reset(); - const pluginData = await plugins.toggleInstall(data.id, data.version); - await events.log({ - type: 'plugin-' + (pluginData.installed ? 'install' : 'uninstall'), - text: data.id, - version: data.version, - uid: socket.uid, - }); - return pluginData; -}; - -SocketAdmin.plugins.getActive = function (socket, data, callback) { - plugins.getActive(callback); -}; - -SocketAdmin.plugins.orderActivePlugins = async function (socket, data) { - data = data.filter(plugin => plugin && plugin.name); - await Promise.all(data.map(plugin => db.sortedSetAdd('plugins:active', plugin.order || 0, plugin.name))); -}; - -SocketAdmin.plugins.upgrade = function (socket, data, callback) { - plugins.upgrade(data.id, data.version, callback); -}; - -SocketAdmin.widgets.set = function (socket, data, callback) { - if (!Array.isArray(data)) { - return callback(new Error('[[error:invalid-data]]')); - } - - async.eachSeries(data, widgets.setArea, callback); -}; - -SocketAdmin.config.set = async function (socket, data) { - if (!data) { - throw new Error('[[error:invalid-data]]'); - } - const _data = {}; - _data[data.key] = data.value; - await SocketAdmin.config.setMultiple(socket, _data); -}; - -SocketAdmin.config.setMultiple = async function (socket, data) { - if (!data) { - throw new Error('[[error:invalid-data]]'); - } - - const changes = {}; - const newData = meta.configs.serialize(data); - const oldData = meta.configs.serialize(meta.config); - Object.keys(newData).forEach(function (key) { - if (newData[key] !== oldData[key]) { - changes[key] = newData[key]; - changes[key + '_old'] = meta.config[key]; - } - }); - await meta.configs.setMultiple(data); - for (const field in data) { - if (data.hasOwnProperty(field)) { - const setting = { - key: field, - value: data[field], - }; - plugins.fireHook('action:config.set', setting); - logger.monitorConfig({ io: index.server }, setting); - } - } - if (Object.keys(changes).length) { - changes.type = 'config-change'; - changes.uid = socket.uid; - changes.ip = socket.ip; - await events.log(changes); - } -}; - -SocketAdmin.config.remove = function (socket, key, callback) { - meta.configs.remove(key, callback); -}; - -SocketAdmin.settings.get = function (socket, data, callback) { - meta.settings.get(data.hash, callback); -}; - -SocketAdmin.settings.set = async function (socket, data) { - await meta.settings.set(data.hash, data.values); - const eventData = data.values; - eventData.type = 'settings-change'; - eventData.uid = socket.uid; - eventData.ip = socket.ip; - eventData.hash = data.hash; - await events.log(eventData); -}; - -SocketAdmin.settings.clearSitemapCache = function (socket, data, callback) { - require('../sitemap').clearCache(); - callback(); -}; - -SocketAdmin.email.test = function (socket, data, callback) { - const payload = { - subject: '[[email:test-email.subject]]', - }; - - switch (data.template) { - case 'digest': - userDigest.execute({ - interval: 'alltime', - subscribers: [socket.uid], - }, callback); - break; - - case 'banned': - Object.assign(payload, { - username: 'test-user', - until: utils.toISOString(Date.now()), - reason: 'Test Reason', - }); - emailer.send(data.template, socket.uid, payload, callback); - break; - - case 'welcome': - userEmail.sendValidationEmail(socket.uid, { - force: 1, - }, callback); - break; - - case 'notification': - async.waterfall([ - function (next) { - notifications.create({ - type: 'test', - bodyShort: '[[email:notif.test.short]]', - bodyLong: '[[email:notif.test.long]]', - nid: 'uid:' + socket.uid + ':test', - path: '/', - from: socket.uid, - }, next); - }, - function (notifObj, next) { - emailer.send('notification', socket.uid, { - path: notifObj.path, - subject: utils.stripHTMLTags(notifObj.subject || '[[notifications:new_notification]]'), - intro: utils.stripHTMLTags(notifObj.bodyShort), - body: notifObj.bodyLong || '', - notification: notifObj, - showUnsubscribe: true, - }, next); - }, - ], callback); - break; - - default: - emailer.send(data.template, socket.uid, payload, callback); - break; - } -}; - -SocketAdmin.analytics.get = function (socket, data, callback) { - if (!data || !data.graph || !data.units) { - return callback(new Error('[[error:invalid-data]]')); - } - - // Default returns views from past 24 hours, by hour - if (!data.amount) { - if (data.units === 'days') { - data.amount = 30; - } else { - data.amount = 24; - } - } - const getStats = data.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; - if (data.graph === 'traffic') { - async.parallel({ - uniqueVisitors: function (next) { - getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount, next); - }, - pageviews: function (next) { - getStats('analytics:pageviews', data.until || Date.now(), data.amount, next); - }, - pageviewsRegistered: function (next) { - getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount, next); - }, - pageviewsGuest: function (next) { - getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount, next); - }, - pageviewsBot: function (next) { - getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount, next); - }, - summary: function (next) { - analytics.getSummary(next); - }, - }, function (err, data) { - data.pastDay = data.pageviews.reduce(function (a, b) { return parseInt(a, 10) + parseInt(b, 10); }); - data.pageviews[data.pageviews.length - 1] = parseInt(data.pageviews[data.pageviews.length - 1], 10) + analytics.getUnwrittenPageviews(); - callback(err, data); - }); - } -}; - -SocketAdmin.logs.get = function (socket, data, callback) { - meta.logs.get(callback); -}; - -SocketAdmin.logs.clear = function (socket, data, callback) { - meta.logs.clear(callback); -}; - -SocketAdmin.errors.clear = function (socket, data, callback) { - meta.errors.clear(callback); -}; - SocketAdmin.getSearchDict = async function (socket) { const settings = await user.getSettings(socket.uid); const lang = settings.userLang || meta.config.defaultLang || 'en-GB'; @@ -344,31 +92,4 @@ SocketAdmin.reloadAllSessions = function (socket, data, callback) { callback(); }; -SocketAdmin.uploads.delete = function (socket, pathToFile, callback) { - pathToFile = path.join(nconf.get('upload_path'), pathToFile); - if (!pathToFile.startsWith(nconf.get('upload_path'))) { - return callback(new Error('[[error:invalid-path]]')); - } - - fs.unlink(pathToFile, callback); -}; - -SocketAdmin.digest.resend = async (socket, data) => { - const uid = data.uid; - const interval = data.action.startsWith('resend-') ? data.action.slice(7) : await userDigest.getUsersInterval(uid); - - if (!interval && meta.config.dailyDigestFreq === 'off') { - throw new Error('[[error:digest-not-enabled]]'); - } - - if (uid) { - await userDigest.execute({ - interval: interval || meta.config.dailyDigestFreq, - subscribers: [uid], - }); - } else { - await userDigest.execute({ interval: interval }); - } -}; - require('../promisify')(SocketAdmin); diff --git a/src/socket.io/admin/analytics.js b/src/socket.io/admin/analytics.js new file mode 100644 index 0000000000..16cc82ec6d --- /dev/null +++ b/src/socket.io/admin/analytics.js @@ -0,0 +1,47 @@ +'use strict'; + +const async = require('async'); +const analytics = require('../../analytics'); +const Analytics = module.exports; + +Analytics.get = function (socket, data, callback) { + if (!data || !data.graph || !data.units) { + return callback(new Error('[[error:invalid-data]]')); + } + + // Default returns views from past 24 hours, by hour + if (!data.amount) { + if (data.units === 'days') { + data.amount = 30; + } else { + data.amount = 24; + } + } + const getStats = data.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; + if (data.graph === 'traffic') { + async.parallel({ + uniqueVisitors: function (next) { + getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount, next); + }, + pageviews: function (next) { + getStats('analytics:pageviews', data.until || Date.now(), data.amount, next); + }, + pageviewsRegistered: function (next) { + getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount, next); + }, + pageviewsGuest: function (next) { + getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount, next); + }, + pageviewsBot: function (next) { + getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount, next); + }, + summary: function (next) { + analytics.getSummary(next); + }, + }, function (err, data) { + data.pastDay = data.pageviews.reduce(function (a, b) { return parseInt(a, 10) + parseInt(b, 10); }); + data.pageviews[data.pageviews.length - 1] = parseInt(data.pageviews[data.pageviews.length - 1], 10) + analytics.getUnwrittenPageviews(); + callback(err, data); + }); + } +}; diff --git a/src/socket.io/admin/config.js b/src/socket.io/admin/config.js new file mode 100644 index 0000000000..63ab6d149c --- /dev/null +++ b/src/socket.io/admin/config.js @@ -0,0 +1,55 @@ +'use strict'; + +const meta = require('../../meta'); +const plugins = require('../../plugins'); +const logger = require('../../logger'); +const events = require('../../events'); +const index = require('../index'); + +const Config = module.exports; + +Config.set = async function (socket, data) { + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + const _data = {}; + _data[data.key] = data.value; + await Config.setMultiple(socket, _data); +}; + +Config.setMultiple = async function (socket, data) { + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + + const changes = {}; + const newData = meta.configs.serialize(data); + const oldData = meta.configs.serialize(meta.config); + Object.keys(newData).forEach(function (key) { + if (newData[key] !== oldData[key]) { + changes[key] = newData[key]; + changes[key + '_old'] = meta.config[key]; + } + }); + await meta.configs.setMultiple(data); + for (const field in data) { + if (data.hasOwnProperty(field)) { + const setting = { + key: field, + value: data[field], + }; + plugins.fireHook('action:config.set', setting); + logger.monitorConfig({ io: index.server }, setting); + } + } + if (Object.keys(changes).length) { + changes.type = 'config-change'; + changes.uid = socket.uid; + changes.ip = socket.ip; + await events.log(changes); + } +}; + +Config.remove = function (socket, key, callback) { + meta.configs.remove(key, callback); +}; diff --git a/src/socket.io/admin/digest.js b/src/socket.io/admin/digest.js new file mode 100644 index 0000000000..b6427aced6 --- /dev/null +++ b/src/socket.io/admin/digest.js @@ -0,0 +1,24 @@ +'use strict'; + +const meta = require('../../meta'); +const userDigest = require('../../user/digest'); + +const Digest = module.exports; + +Digest.resend = async (socket, data) => { + const uid = data.uid; + const interval = data.action.startsWith('resend-') ? data.action.slice(7) : await userDigest.getUsersInterval(uid); + + if (!interval && meta.config.dailyDigestFreq === 'off') { + throw new Error('[[error:digest-not-enabled]]'); + } + + if (uid) { + await userDigest.execute({ + interval: interval || meta.config.dailyDigestFreq, + subscribers: [uid], + }); + } else { + await userDigest.execute({ interval: interval }); + } +}; diff --git a/src/socket.io/admin/email.js b/src/socket.io/admin/email.js new file mode 100644 index 0000000000..911613ba99 --- /dev/null +++ b/src/socket.io/admin/email.js @@ -0,0 +1,69 @@ +'use strict'; + +const async = require('async'); +const userDigest = require('../../user/digest'); +const userEmail = require('../../user/email'); +const notifications = require('../../notifications'); +const emailer = require('../../emailer'); +const utils = require('../../../public/src/utils'); + +const Email = module.exports; + +Email.test = function (socket, data, callback) { + const payload = { + subject: '[[email:test-email.subject]]', + }; + + switch (data.template) { + case 'digest': + userDigest.execute({ + interval: 'alltime', + subscribers: [socket.uid], + }, callback); + break; + + case 'banned': + Object.assign(payload, { + username: 'test-user', + until: utils.toISOString(Date.now()), + reason: 'Test Reason', + }); + emailer.send(data.template, socket.uid, payload, callback); + break; + + case 'welcome': + userEmail.sendValidationEmail(socket.uid, { + force: 1, + }, callback); + break; + + case 'notification': + async.waterfall([ + function (next) { + notifications.create({ + type: 'test', + bodyShort: '[[email:notif.test.short]]', + bodyLong: '[[email:notif.test.long]]', + nid: 'uid:' + socket.uid + ':test', + path: '/', + from: socket.uid, + }, next); + }, + function (notifObj, next) { + emailer.send('notification', socket.uid, { + path: notifObj.path, + subject: utils.stripHTMLTags(notifObj.subject || '[[notifications:new_notification]]'), + intro: utils.stripHTMLTags(notifObj.bodyShort), + body: notifObj.bodyLong || '', + notification: notifObj, + showUnsubscribe: true, + }, next); + }, + ], callback); + break; + + default: + emailer.send(data.template, socket.uid, payload, callback); + break; + } +}; diff --git a/src/socket.io/admin/errors.js b/src/socket.io/admin/errors.js new file mode 100644 index 0000000000..ba19ae8339 --- /dev/null +++ b/src/socket.io/admin/errors.js @@ -0,0 +1,8 @@ +'use strict'; + +const meta = require('../../meta'); +const Errors = module.exports; + +Errors.clear = function (socket, data, callback) { + meta.errors.clear(callback); +}; diff --git a/src/socket.io/admin/logs.js b/src/socket.io/admin/logs.js new file mode 100644 index 0000000000..22bcbe71a7 --- /dev/null +++ b/src/socket.io/admin/logs.js @@ -0,0 +1,12 @@ +'use strict'; + +const meta = require('../../meta'); +const Logs = module.exports; + +Logs.get = function (socket, data, callback) { + meta.logs.get(callback); +}; + +Logs.clear = function (socket, data, callback) { + meta.logs.clear(callback); +}; diff --git a/src/socket.io/admin/plugins.js b/src/socket.io/admin/plugins.js new file mode 100644 index 0000000000..16f112130d --- /dev/null +++ b/src/socket.io/admin/plugins.js @@ -0,0 +1,43 @@ +'use strict'; + +const plugins = require('../../plugins'); +const events = require('../../events'); +const db = require('../../database'); + +const Plugins = module.exports; + +Plugins.toggleActive = async function (socket, plugin_id) { + require('../../posts/cache').reset(); + const data = await plugins.toggleActive(plugin_id); + await events.log({ + type: 'plugin-' + (data.active ? 'activate' : 'deactivate'), + text: plugin_id, + uid: socket.uid, + }); + return data; +}; + +Plugins.toggleInstall = async function (socket, data) { + require('../../posts/cache').reset(); + const pluginData = await plugins.toggleInstall(data.id, data.version); + await events.log({ + type: 'plugin-' + (pluginData.installed ? 'install' : 'uninstall'), + text: data.id, + version: data.version, + uid: socket.uid, + }); + return pluginData; +}; + +Plugins.getActive = function (socket, data, callback) { + plugins.getActive(callback); +}; + +Plugins.orderActivePlugins = async function (socket, data) { + data = data.filter(plugin => plugin && plugin.name); + await Promise.all(data.map(plugin => db.sortedSetAdd('plugins:active', plugin.order || 0, plugin.name))); +}; + +Plugins.upgrade = function (socket, data, callback) { + plugins.upgrade(data.id, data.version, callback); +}; diff --git a/src/socket.io/admin/settings.js b/src/socket.io/admin/settings.js new file mode 100644 index 0000000000..063c8ca4ee --- /dev/null +++ b/src/socket.io/admin/settings.js @@ -0,0 +1,24 @@ +'use strict'; + +const meta = require('../../meta'); +const events = require('../../events'); +const Settings = module.exports; + +Settings.get = function (socket, data, callback) { + meta.settings.get(data.hash, callback); +}; + +Settings.set = async function (socket, data) { + await meta.settings.set(data.hash, data.values); + const eventData = data.values; + eventData.type = 'settings-change'; + eventData.uid = socket.uid; + eventData.ip = socket.ip; + eventData.hash = data.hash; + await events.log(eventData); +}; + +Settings.clearSitemapCache = function (socket, data, callback) { + require('../../sitemap').clearCache(); + callback(); +}; diff --git a/src/socket.io/admin/themes.js b/src/socket.io/admin/themes.js new file mode 100644 index 0000000000..1a6b4bd23b --- /dev/null +++ b/src/socket.io/admin/themes.js @@ -0,0 +1,24 @@ +'use strict'; + +const meta = require('../../meta'); +const widgets = require('../../widgets'); + +const Themes = module.exports; + +Themes.getInstalled = function (socket, data, callback) { + meta.themes.get(callback); +}; + +Themes.set = async function (socket, data) { + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + if (data.type === 'local') { + await widgets.reset(); + } + + data.ip = socket.ip; + data.uid = socket.uid; + + await meta.themes.set(data); +}; diff --git a/src/socket.io/admin/uploads.js b/src/socket.io/admin/uploads.js new file mode 100644 index 0000000000..d34edba503 --- /dev/null +++ b/src/socket.io/admin/uploads.js @@ -0,0 +1,16 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const nconf = require('nconf'); + +const Uploads = module.exports; + +Uploads.delete = function (socket, pathToFile, callback) { + pathToFile = path.join(nconf.get('upload_path'), pathToFile); + if (!pathToFile.startsWith(nconf.get('upload_path'))) { + return callback(new Error('[[error:invalid-path]]')); + } + + fs.unlink(pathToFile, callback); +}; diff --git a/src/socket.io/admin/widgets.js b/src/socket.io/admin/widgets.js new file mode 100644 index 0000000000..58a2a019d6 --- /dev/null +++ b/src/socket.io/admin/widgets.js @@ -0,0 +1,13 @@ +'use strict'; + +const async = require('async'); +const widgets = require('../../widgets'); +const Widgets = module.exports; + +Widgets.set = function (socket, data, callback) { + if (!Array.isArray(data)) { + return callback(new Error('[[error:invalid-data]]')); + } + + async.eachSeries(data, widgets.setArea, callback); +}; diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 7b8e383a85..d0b78f2880 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -342,8 +342,20 @@ SocketGroups.loadMoreMembers = async (socket, data) => { if (!data.groupName || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { throw new Error('[[error:invalid-data]]'); } + const [isHidden, isAdmin, isGlobalMod] = await Promise.all([ + groups.isHidden(data.groupName), + user.isAdministrator(socket.uid), + user.isGlobalModerator(socket.uid), + ]); + if (isHidden && !isAdmin && !isGlobalMod) { + const isMember = await groups.isMember(socket.uid, data.groupName); + if (!isMember) { + throw new Error('[[error:no-privileges]]'); + } + } + data.after = parseInt(data.after, 10); - const users = await user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9); + const users = await groups.getOwnersAndMembers(data.groupName, socket.uid, data.after, data.after + 9); return { users: users, nextStart: data.after + 10, diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index f44da3ad68..b9d7988bbd 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -8,7 +8,7 @@ const notifications = require('../notifications'); const plugins = require('../plugins'); const Messaging = require('../messaging'); const utils = require('../utils'); -const server = require('./'); +const server = require('./index'); const user = require('../user'); const privileges = require('../privileges'); @@ -196,7 +196,7 @@ SocketModules.chats.delete = async function (socket, data) { throw new Error('[[error:invalid-data]]'); } await Messaging.canDelete(data.messageId, socket.uid); - await Messaging.deleteMessage(data.messageId); + await Messaging.deleteMessage(data.messageId, socket.uid); }; SocketModules.chats.restore = async function (socket, data) { @@ -204,7 +204,7 @@ SocketModules.chats.restore = async function (socket, data) { throw new Error('[[error:invalid-data]]'); } await Messaging.canDelete(data.messageId, socket.uid); - await Messaging.restoreMessage(data.messageId); + await Messaging.restoreMessage(data.messageId, socket.uid); }; SocketModules.chats.canMessage = async function (socket, roomId) { diff --git a/src/socket.io/posts/move.js b/src/socket.io/posts/move.js index fe8a293738..bfd614b063 100644 --- a/src/socket.io/posts/move.js +++ b/src/socket.io/posts/move.js @@ -2,6 +2,7 @@ const privileges = require('../../privileges'); const topics = require('../../topics'); +const posts = require('../../posts'); const socketHelpers = require('../helpers'); module.exports = function (SocketPosts) { @@ -25,7 +26,15 @@ module.exports = function (SocketPosts) { throw new Error('[[error:no-privileges]]'); } await topics.movePostToTopic(socket.uid, pid, data.tid); - socketHelpers.sendNotificationToPostOwner(pid, socket.uid, 'move', 'notifications:moved_your_post'); + + const [postDeleted, topicDeleted] = await Promise.all([ + posts.getPostField(pid, 'deleted'), + topics.getTopicField(data.tid, 'deleted'), + ]); + + if (!postDeleted && !topicDeleted) { + socketHelpers.sendNotificationToPostOwner(pid, socket.uid, 'move', 'notifications:moved_your_post'); + } } }; }; diff --git a/src/socket.io/topics/move.js b/src/socket.io/topics/move.js index 0f309eeac8..47ef260857 100644 --- a/src/socket.io/topics/move.js +++ b/src/socket.io/topics/move.js @@ -17,13 +17,14 @@ module.exports = function (SocketTopics) { if (!canMove) { throw new Error('[[error:no-privileges]]'); } - const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'slug']); + const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'slug', 'deleted']); data.uid = socket.uid; await topics.tools.move(tid, data); socketHelpers.emitToTopicAndCategory('event:topic_moved', topicData); - - socketHelpers.sendNotificationToTopicOwner(tid, socket.uid, 'move', 'notifications:moved_your_topic'); + if (!topicData.deleted) { + socketHelpers.sendNotificationToTopicOwner(tid, socket.uid, 'move', 'notifications:moved_your_topic'); + } }); }; diff --git a/src/socket.io/user.js b/src/socket.io/user.js index a57885a76f..5a55769001 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -49,6 +49,9 @@ SocketUser.deleteAccount = async function (socket, data) { if (isAdmin) { throw new Error('[[error:cant-delete-admin]]'); } + if (meta.config.allowAccountDelete !== 1) { + throw new Error('[[error:no-privileges]]'); + } const userData = await user.deleteAccount(socket.uid); require('./index').server.sockets.emit('event:user_status_change', { uid: socket.uid, status: 'offline' }); diff --git a/src/topics/data.js b/src/topics/data.js index 44625c47f2..2e1931e823 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -106,4 +106,8 @@ function modifyTopic(topic, fields) { if (topic.hasOwnProperty('upvotes') && topic.hasOwnProperty('downvotes')) { topic.votes = topic.upvotes - topic.downvotes; } + + if (fields.includes('teaserPid') || !fields.length) { + topic.teaserPid = topic.teaserPid || null; + } } diff --git a/src/topics/fork.js b/src/topics/fork.js index 53cecfaa7c..40edecf6b8 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -5,6 +5,7 @@ const async = require('async'); const db = require('../database'); const posts = require('../posts'); +const categories = require('../categories'); const privileges = require('../privileges'); const plugins = require('../plugins'); const meta = require('../meta'); @@ -101,6 +102,7 @@ module.exports = function (Topics) { await db.sortedSetIncrBy('cid:' + topicData[1].cid + ':tids:posts', 1, toTid); } if (topicData[0].cid === topicData[1].cid) { + await categories.updateRecentTidForCid(topicData[0].cid); return; } const removeFrom = [ @@ -118,6 +120,11 @@ module.exports = function (Topics) { if (postData.votes > 0) { tasks.push(db.sortedSetAdd('cid:' + topicData[1].cid + ':uid:' + postData.uid + ':pids:votes', postData.votes, postData.pid)); } + await Promise.all(tasks); + await Promise.all([ + categories.updateRecentTidForCid(topicData[0].cid), + categories.updateRecentTidForCid(topicData[1].cid), + ]); } }; diff --git a/src/topics/index.js b/src/topics/index.js index 57f738b282..30c645f458 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -67,7 +67,12 @@ Topics.getTopicsByTids = async function (tids, options) { const uids = _.uniq(topics.map(t => t && t.uid && t.uid.toString()).filter(v => utils.isNumber(v))); const cids = _.uniq(topics.map(t => t && t.cid && t.cid.toString()).filter(v => utils.isNumber(v))); + const guestTopics = topics.filter(t => t.uid === 0); + async function loadGuestHandles() { + return await Promise.all(guestTopics.map(topic => posts.getPostField(topic.mainPid, 'handle'))); + } const [ + callerSettings, users, userSettings, categoriesData, @@ -76,7 +81,9 @@ Topics.getTopicsByTids = async function (tids, options) { bookmarks, teasers, tags, + guestHandles, ] = await Promise.all([ + user.getSettings(uid), user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status']), user.getMultipleUserSettings(uids), categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'image', 'imageClass', 'bgColor', 'color', 'disabled']), @@ -85,33 +92,39 @@ Topics.getTopicsByTids = async function (tids, options) { Topics.getUserBookmarks(tids, uid), Topics.getTeasers(topics, options), Topics.getTopicsTagsObjects(tids), + loadGuestHandles(), ]); - users.forEach(function (user, index) { - if (meta.config.hideFullname || !userSettings[index].showfullname) { - user.fullname = undefined; + users.forEach((userObj, idx) => { + // Hide fullname if needed + if (meta.config.hideFullname || !userSettings[idx].showfullname) { + userObj.fullname = undefined; } }); const usersMap = _.zipObject(uids, users); const categoriesMap = _.zipObject(cids, categoriesData); + const tidToGuestHandle = _.zipObject(guestTopics.map(t => t.tid), guestHandles); + const sortOldToNew = callerSettings.topicPostSort === 'newest_to_oldest'; + topics.forEach(function (topic, i) { + if (topic) { + topic.category = categoriesMap[topic.cid]; + topic.user = usersMap[topic.uid]; + if (tidToGuestHandle[topic.tid]) { + topic.user.username = tidToGuestHandle[topic.tid]; + } + topic.teaser = teasers[i] || null; + topic.tags = tags[i]; - for (var i = 0; i < topics.length; i += 1) { - if (topics[i]) { - topics[i].category = categoriesMap[topics[i].cid]; - topics[i].user = usersMap[topics[i].uid]; - topics[i].teaser = teasers[i]; - topics[i].tags = tags[i]; + topic.isOwner = topic.uid === parseInt(uid, 10); + topic.ignored = isIgnored[i]; + topic.unread = !hasRead[i] && !isIgnored[i]; + topic.bookmark = sortOldToNew ? Math.max(1, topic.postcount + 2 - bookmarks[i]) : bookmarks[i]; + topic.unreplied = !topic.teaser; - topics[i].isOwner = topics[i].uid === parseInt(uid, 10); - topics[i].ignored = isIgnored[i]; - topics[i].unread = !hasRead[i] && !isIgnored[i]; - topics[i].bookmark = bookmarks[i]; - topics[i].unreplied = !topics[i].teaser; - - topics[i].icons = []; + topic.icons = []; } - } + }); topics = topics.filter(topic => topic && topic.category && !topic.category.disabled); diff --git a/src/topics/suggested.js b/src/topics/suggested.js index d0cd9f1eea..abf904f19e 100644 --- a/src/topics/suggested.js +++ b/src/topics/suggested.js @@ -17,26 +17,25 @@ module.exports = function (Topics) { getSearchTids(tid, uid), ]); - tids = tagTids.concat(searchTids); - tids = tids.filter(_tid => _tid !== tid); - tids = _.shuffle(_.uniq(tids)); + tids = tagTids.concat(searchTids).filter(_tid => _tid !== tid); let categoryTids = []; if (stop !== -1 && tids.length < stop - start + 1) { categoryTids = await getCategoryTids(tid); } - tids = _.uniq(tids.concat(categoryTids)).slice(start, stop !== -1 ? stop + 1 : undefined); + tids = _.shuffle(_.uniq(tids.concat(categoryTids))); tids = await privileges.topics.filterTids('topics:read', tids, uid); let topicData = await Topics.getTopicsByTids(tids, uid); topicData = topicData.filter(topic => topic && !topic.deleted && topic.tid !== tid); topicData = await user.blocks.filter(uid, topicData); + topicData = topicData.slice(start, stop !== -1 ? stop + 1 : undefined); return topicData; }; async function getTidsWithSameTags(tid) { const tags = await Topics.getTopicTags(tid); const tids = await db.getSortedSetRevRange(tags.map(tag => 'tag:' + tag + ':topics'), 0, -1); - return _.uniq(tids).map(Number); + return _.shuffle(_.uniq(tids)).slice(0, 10).map(Number); } async function getSearchTids(tid, uid) { @@ -49,7 +48,7 @@ module.exports = function (Topics) { uid: uid, returnIds: true, }); - return _.shuffle(data.tids).slice(0, 20).map(Number); + return _.shuffle(data.tids).slice(0, 10).map(Number); } async function getCategoryTids(tid) { diff --git a/src/topics/tags.js b/src/topics/tags.js index 22fb2abae4..192d5e8a26 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -46,8 +46,6 @@ module.exports = function (Topics) { if (!tag) { throw new Error('[[error:invalid-tag]]'); } - - tag = utils.cleanUpTag(tag, meta.config.maximumTagLength); if (tag.length < (meta.config.minimumTagLength || 3)) { throw new Error('[[error:tag-too-short]]'); } @@ -77,6 +75,7 @@ module.exports = function (Topics) { if (!newTagName || tag === newTagName) { return; } + newTagName = utils.cleanUpTag(newTagName, meta.config.maximumTagLength); await Topics.createEmptyTag(newTagName); await batch.processSortedSet('tag:' + tag + ':topics', async function (tids) { const scores = await db.sortedSetScores('tag:' + tag + ':topics', tids); @@ -299,7 +298,8 @@ module.exports = function (Topics) { Topics.getRelatedTopics = async function (topicData, uid) { if (plugins.hasListeners('filter:topic.getRelatedTopics')) { - return await plugins.fireHook('filter:topic.getRelatedTopics', { topic: topicData, uid: uid }); + const result = await plugins.fireHook('filter:topic.getRelatedTopics', { topic: topicData, uid: uid, topics: [] }); + return result.topics; } let maximumTopics = meta.config.maximumRelatedTopics; diff --git a/src/topics/tools.js b/src/topics/tools.js index 6a1fdf1cd7..2fdd0d445c 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -64,7 +64,7 @@ module.exports = function (Topics) { topicTools.purge = async function (tid, uid) { const topicData = await Topics.getTopicData(tid); if (!topicData) { - return; + throw new Error('[[error:no-topic]]'); } const canPurge = await privileges.topics.canPurge(tid, uid); if (!canPurge) { @@ -138,7 +138,8 @@ module.exports = function (Topics) { await Promise.all(promises); - topicData.isPinned = pin; + topicData.isPinned = pin; // deprecate in v2.0 + topicData.pinned = pin; plugins.fireHook('action:topic.pin', { topic: _.clone(topicData), uid: uid }); diff --git a/src/upgrade.js b/src/upgrade.js index 7975a9ba6d..73d489bcba 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -7,7 +7,7 @@ var readline = require('readline'); var winston = require('winston'); var db = require('./database'); -var file = require('../src/file'); +var file = require('./file'); /* * Need to write an upgrade script for NodeBB? Cool. diff --git a/src/upgrades/1.13.3/fix_users_sorted_sets.js b/src/upgrades/1.13.3/fix_users_sorted_sets.js new file mode 100644 index 0000000000..3a6b947fa4 --- /dev/null +++ b/src/upgrades/1.13.3/fix_users_sorted_sets.js @@ -0,0 +1,63 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + +module.exports = { + name: 'Fix user sorted sets', + timestamp: Date.UTC(2020, 4, 2), + method: async function (callback) { + const progress = this.progress; + const nextUid = await db.getObjectField('global', 'nextUid'); + const allUids = []; + for (let i = 1; i <= nextUid; i++) { + allUids.push(i); + } + + progress.total = nextUid; + let totalUserCount = 0; + + await db.delete('user:null'); + await db.sortedSetsRemove([ + 'users:joindate', + 'users:reputation', + 'users:postcount', + 'users:flags', + ], 'null'); + + await batch.processArray(allUids, async function (uids) { + progress.incr(uids.length); + const userData = await db.getObjects(uids.map(id => 'user:' + id)); + + await Promise.all(userData.map(async function (userData, index) { + if (!userData || !userData.uid) { + await db.sortedSetsRemove([ + 'users:joindate', + 'users:reputation', + 'users:postcount', + 'users:flags', + ], uids[index]); + if (userData && !userData.uid) { + await db.delete('user:' + uids[index]); + } + return; + } + totalUserCount += 1; + await db.sortedSetAddBulk([ + ['users:joindate', userData.joindate, uids[index]], + ['users:reputation', userData.reputation, uids[index]], + ['users:postcount', userData.postcount, uids[index]], + ]); + if (userData.hasOwnProperty('flags') && parseInt(userData.flags, 10) > 0) { + await db.sortedSetAdd('users:flags', userData.flags, uids[index]); + } + })); + }, { + progress: progress, + batch: 500, + }); + + await db.setObjectField('global', 'userCount', totalUserCount); + callback(); + }, +}; diff --git a/src/user/admin.js b/src/user/admin.js index 9880af3edd..e48a86e676 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -6,6 +6,7 @@ const validator = require('validator'); const db = require('../database'); const plugins = require('../plugins'); +const batch = require('../batch'); module.exports = function (User) { User.logIP = async function (uid, ip) { @@ -29,15 +30,17 @@ module.exports = function (User) { User.getUsersCSV = async function () { winston.verbose('[user/getUsersCSV] Compiling User CSV data'); - let csvContent = ''; - const uids = await db.getSortedSetRange('users:joindate', 0, -1); + const data = await plugins.fireHook('filter:user.csvFields', { fields: ['uid', 'email', 'username'] }); - const usersData = await User.getUsersFields(uids, data.fields); - usersData.forEach(function (user) { - if (user) { - csvContent += user.email + ',' + user.username + ',' + user.uid + '\n'; - } - }); + let csvContent = data.fields.join(',') + '\n'; + await batch.processSortedSet('users:joindate', async (uids) => { + const usersData = await User.getUsersFields(uids, data.fields); + csvContent += usersData.reduce((memo, user) => { + memo += user.email + ',' + user.username + ',' + user.uid + '\n'; + return memo; + }, ''); + }, {}); + return csvContent; }; }; diff --git a/src/user/digest.js b/src/user/digest.js index b0051a9977..cebabccf29 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -7,7 +7,7 @@ const nconf = require('nconf'); const db = require('../database'); const batch = require('../batch'); const meta = require('../meta'); -const user = require('../user'); +const user = require('./index'); const topics = require('../topics'); const plugins = require('../plugins'); const emailer = require('../emailer'); diff --git a/src/user/email.js b/src/user/email.js index 1a76974c59..9e75a40f48 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -3,7 +3,7 @@ var nconf = require('nconf'); -var user = require('../user'); +var user = require('./index'); var utils = require('../utils'); var plugins = require('../plugins'); var db = require('../database'); diff --git a/src/user/index.js b/src/user/index.js index e25e10db44..8afc88bf55 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -41,7 +41,7 @@ require('./blocks')(User); require('./uploads')(User); User.exists = async function (uid) { - return await db.exists('user:' + uid); + return await db.isSortedSetMember('users:joindate', uid); }; User.existsBySlug = async function (userslug) { @@ -231,92 +231,75 @@ User.addInterstitials = function (callback) { hook: 'filter:register.interstitial', method: [ // GDPR information collection/processing consent + email consent - function (data, callback) { - if (!meta.config.gdpr_enabled) { - return setImmediate(callback, null, data); + async function (data) { + if (!meta.config.gdpr_enabled || (data.userData && data.userData.gdpr_consent)) { + return data; } if (!data.userData) { - return setImmediate(callback, new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } - const add = function () { - data.interstitials.push({ - template: 'partials/gdpr_consent', - data: { - digestFrequency: meta.config.dailyDigestFreq, - digestEnabled: meta.config.dailyDigestFreq !== 'off', - }, - callback: function (userData, formData, next) { - if (formData.gdpr_agree_data === 'on' && formData.gdpr_agree_email === 'on') { - userData.gdpr_consent = true; - } - next(userData.gdpr_consent ? null : new Error('[[register:gdpr_consent_denied]]')); - }, - }); - }; - - if (!data.userData.gdpr_consent) { - if (data.userData.uid) { - db.getObjectField('user:' + data.userData.uid, 'gdpr_consent', function (err, consented) { - if (err) { - return callback(err); - } else if (!parseInt(consented, 10)) { - add(); - } - - callback(null, data); - }); - } else { - add(); - setImmediate(callback, null, data); + if (data.userData.uid) { + const consented = await db.getObjectField('user:' + data.userData.uid, 'gdpr_consent'); + if (parseInt(consented, 10)) { + return data; } - } else { - // GDPR consent signed - setImmediate(callback, null, data); } + + data.interstitials.push({ + template: 'partials/gdpr_consent', + data: { + digestFrequency: meta.config.dailyDigestFreq, + digestEnabled: meta.config.dailyDigestFreq !== 'off', + }, + callback: function (userData, formData, next) { + if (formData.gdpr_agree_data === 'on' && formData.gdpr_agree_email === 'on') { + userData.gdpr_consent = true; + } + + next(userData.gdpr_consent ? null : new Error('[[register:gdpr_consent_denied]]')); + }, + }); + return data; }, // Forum Terms of Use - function (data, callback) { + async function (data) { if (!data.userData) { - return setImmediate(callback, new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); + } + if (!meta.config.termsOfUse || data.userData.acceptTos) { + // no ToS or ToS accepted, nothing to do + return data; } - const add = function () { - data.interstitials.push({ - template: 'partials/acceptTos', - data: { - termsOfUse: meta.config.termsOfUse, - }, - callback: function (userData, formData, next) { - if (formData['agree-terms'] === 'on') { - userData.acceptTos = true; - } - - next(userData.acceptTos ? null : new Error('[[register:terms_of_use_error]]')); - }, - }); - }; - - if (meta.config.termsOfUse && !data.userData.acceptTos) { - if (data.userData.uid) { - db.getObjectField('user:' + data.userData.uid, 'acceptTos', function (err, accepted) { - if (err) { - return callback(err); - } else if (!parseInt(accepted, 10)) { - add(); - } - - callback(null, data); - }); - } else { - add(); - setImmediate(callback, null, data); + if (data.userData.uid) { + const accepted = await db.getObjectField('user:' + data.userData.uid, 'acceptTos'); + if (parseInt(accepted, 10)) { + return data; } - } else { - // TOS accepted - setImmediate(callback, null, data); } + + const termsOfUse = await plugins.fireHook('filter:parse.post', { + postData: { + content: meta.config.termsOfUse || '', + }, + }); + + data.interstitials.push({ + template: 'partials/acceptTos', + data: { + termsOfUse: termsOfUse.postData.content, + }, + callback: function (userData, formData, next) { + if (formData['agree-terms'] === 'on') { + userData.acceptTos = true; + } + + next(userData.acceptTos ? null : new Error('[[register:terms_of_use_error]]')); + }, + }); + return data; }, ], }); diff --git a/src/user/info.js b/src/user/info.js index 9eb2268355..48f43e5ebb 100644 --- a/src/user/info.js +++ b/src/user/info.js @@ -4,7 +4,6 @@ var _ = require('lodash'); var validator = require('validator'); var db = require('../database'); -var user = require('../user'); var posts = require('../posts'); var topics = require('../topics'); var utils = require('../../public/src/utils'); @@ -97,12 +96,12 @@ module.exports = function (User) { async function formatBanData(bans) { const banData = await db.getObjects(bans); const uids = banData.map(banData => banData.fromUid); - const usersData = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']); + const usersData = await User.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']); return banData.map(function (banObj, index) { banObj.user = usersData[index]; banObj.until = parseInt(banObj.expire, 10); banObj.untilReadable = new Date(banObj.until).toString(); - banObj.timestampReadable = new Date(banObj.timestamp).toString(); + banObj.timestampReadable = new Date(parseInt(banObj.timestamp, 10)).toString(); banObj.timestampISO = utils.toISOString(banObj.timestamp); banObj.reason = validator.escape(String(banObj.reason || '')) || '[[user:info.banned-no-reason]]'; return banObj; diff --git a/src/user/invite.js b/src/user/invite.js index 8b742263b6..2b0b217cd2 100644 --- a/src/user/invite.js +++ b/src/user/invite.js @@ -5,7 +5,7 @@ var async = require('async'); var nconf = require('nconf'); var validator = require('validator'); -var db = require('./../database'); +var db = require('../database'); var meta = require('../meta'); var emailer = require('../emailer'); var translator = require('../translator'); diff --git a/src/user/notifications.js b/src/user/notifications.js index 4e0cedbac5..fa30b0e2e1 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -174,6 +174,7 @@ UserNotifications.sendTopicNotificationToFollowers = async function (uid, topicD let title = topicData.title; if (title) { title = utils.decodeHTMLEntities(title); + title = title.replace(/,/g, '\\,'); } const notifObj = await notifications.create({ @@ -221,7 +222,7 @@ UserNotifications.sendNameChangeNotification = async function (uid, username) { }; UserNotifications.pushCount = async function (uid) { - const websockets = require('./../socket.io'); + const websockets = require('../socket.io'); const count = await UserNotifications.getUnreadCount(uid); websockets.in('uid_' + uid).emit('event:notifications.updateCount', count); }; diff --git a/src/user/posts.js b/src/user/posts.js index b444c8a41d..e7e9174845 100644 --- a/src/user/posts.js +++ b/src/user/posts.js @@ -67,12 +67,30 @@ module.exports = function (User) { }; User.incrementUserPostCountBy = async function (uid, value) { - const newpostcount = await User.incrementUserFieldBy(uid, 'postcount', value); - if (parseInt(uid, 10) <= 0) { + return await incrementUserFieldAndSetBy(uid, 'postcount', 'users:postcount', value); + }; + + User.incrementUserReputationBy = async function (uid, value) { + return await incrementUserFieldAndSetBy(uid, 'reputation', 'users:reputation', value); + }; + + User.incrementUserFlagsBy = async function (uid, value) { + return await incrementUserFieldAndSetBy(uid, 'flags', 'users:flags', value); + }; + + async function incrementUserFieldAndSetBy(uid, field, set, value) { + value = parseInt(value, 10); + if (!value || !field || !(parseInt(uid, 10) > 0)) { return; } - await db.sortedSetAdd('users:postcount', newpostcount, uid); - }; + const exists = await User.exists(uid); + if (!exists) { + return; + } + const newValue = await User.incrementUserFieldBy(uid, field, value); + await db.sortedSetAdd(set, newValue, uid); + return newValue; + } User.getPostIds = async function (uid, start, stop) { const pids = await db.getSortedSetRevRange('uid:' + uid + ':posts', start, stop); diff --git a/src/user/profile.js b/src/user/profile.js index ac2e7fbcf8..2b6a59e025 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -111,6 +111,9 @@ module.exports = function (User) { if (!data.website) { return; } + if (data.website.length > 255) { + throw new Error('[[error:invalid-website]]'); + } await User.checkMinReputation(callerUid, data.uid, 'min:rep:website'); } @@ -136,13 +139,13 @@ module.exports = function (User) { } function isFullnameValid(data) { - if (data.fullname && validator.isURL(data.fullname)) { + if (data.fullname && (validator.isURL(data.fullname) || data.fullname.length > 255)) { throw new Error('[[error:invalid-fullname]]'); } } function isLocationValid(data) { - if (data.location && validator.isURL(data.location)) { + if (data.location && (validator.isURL(data.location) || data.location.length > 255)) { throw new Error('[[error:invalid-location]]'); } } @@ -159,8 +162,27 @@ module.exports = function (User) { } function isGroupTitleValid(data) { - if (data.groupTitle === 'registered-users' || groups.isPrivilegeGroup(data.groupTitle)) { - throw new Error('[[error:invalid-group-title]]'); + function checkTitle(title) { + if (title === 'registered-users' || groups.isPrivilegeGroup(title)) { + throw new Error('[[error:invalid-group-title]]'); + } + } + if (!data.groupTitle) { + return; + } + let groupTitles = []; + if (validator.isJSON(data.groupTitle)) { + groupTitles = JSON.parse(data.groupTitle); + if (!Array.isArray(groupTitles)) { + throw new Error('[[error:invalid-group-title]]'); + } + groupTitles.forEach(title => checkTitle(title)); + } else { + groupTitles = [data.groupTitle]; + checkTitle(data.groupTitle); + } + if (!meta.config.allowMultipleBadges && groupTitles.length > 1) { + data.groupTitle = JSON.stringify(groupTitles[0]); } } @@ -279,6 +301,6 @@ module.exports = function (User) { User.auth.revokeAllSessions(data.uid), ]); - plugins.fireHook('action:password.change', { uid: uid }); + plugins.fireHook('action:password.change', { uid: uid, targetUid: data.uid }); }; }; diff --git a/src/user/reset.js b/src/user/reset.js index 00baf982b8..d8765b9401 100644 --- a/src/user/reset.js +++ b/src/user/reset.js @@ -3,7 +3,7 @@ var nconf = require('nconf'); var winston = require('winston'); -var user = require('../user'); +var user = require('./index'); var utils = require('../utils'); var batch = require('../batch'); diff --git a/src/user/search.js b/src/user/search.js index 93ae92ddae..e2d4c50fb3 100644 --- a/src/user/search.js +++ b/src/user/search.js @@ -44,7 +44,7 @@ module.exports = function (User) { const userData = await User.getUsers(uids, uid); searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2); - searchResult.users = userData; + searchResult.users = userData.filter(user => user && user.uid > 0); return searchResult; }; diff --git a/src/user/settings.js b/src/user/settings.js index 0a1ca94839..23e0e95f77 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -59,7 +59,7 @@ module.exports = function (User) { settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; settings.bootswatchSkin = validator.escape(String(settings.bootswatchSkin || '')); - settings.homePageRoute = validator.escape(String(settings.homePageRoute || '')); + settings.homePageRoute = validator.escape(String(settings.homePageRoute || '')).replace('/', '/'); settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1; settings.categoryWatchState = getSetting(settings, 'categoryWatchState', 'notwatching'); diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl index 08e8472f94..a3a419ee35 100644 --- a/src/views/admin/development/info.tpl +++ b/src/views/admin/development/info.tpl @@ -1,7 +1,7 @@
    -

    [[admin/development/info:you-are-on, {host}, {port}]]

    +

    [[admin/development/info:you-are-on, {host}, {port}]] • [[admin/development/info:ip, {ip}]]

    diff --git a/src/views/admin/manage/tags.tpl b/src/views/admin/manage/tags.tpl index 56c1c5b8c7..fda5c26e90 100644 --- a/src/views/admin/manage/tags.tpl +++ b/src/views/admin/manage/tags.tpl @@ -8,13 +8,13 @@
    -
    -
    - +
    + {tags.score} - {tags.value} + {tags.valueEscaped}
    diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl index 73f9365ab0..5d111f7366 100644 --- a/src/views/admin/settings/general.tpl +++ b/src/views/admin/settings/general.tpl @@ -8,7 +8,8 @@
    - + +

    @@ -143,8 +144,8 @@

    -
    @@ -157,4 +158,20 @@
    +
    +
    [[admin/settings/general:site-colors]]
    +
    + + + + + + +

    + [[admin/settings/general:background-color-help]] +

    + +
    +
    + \ No newline at end of file diff --git a/src/views/admin/settings/group.tpl b/src/views/admin/settings/group.tpl index 5b7a104d2e..cbfe08339c 100644 --- a/src/views/admin/settings/group.tpl +++ b/src/views/admin/settings/group.tpl @@ -21,7 +21,7 @@
    diff --git a/src/views/emails/digest.tpl b/src/views/emails/digest.tpl index 424748406b..264d632c49 100644 --- a/src/views/emails/digest.tpl +++ b/src/views/emails/digest.tpl @@ -55,7 +55,7 @@ {function.renderDigestAvatar}

    {recent.title}

    -

    {recent.teaser.user.username}

    +

    {recent.teaser.user.username}

    diff --git a/src/webserver.js b/src/webserver.js index dc506c45ff..45af13b745 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -206,24 +206,9 @@ function configureBodyParser(app) { } function setupCookie() { - var ttl = meta.getSessionTTLSeconds() * 1000; - - var cookie = { - maxAge: ttl, - }; - - if (nconf.get('cookieDomain') || meta.config.cookieDomain) { - cookie.domain = nconf.get('cookieDomain') || meta.config.cookieDomain; - } - - if (nconf.get('secure')) { - cookie.secure = true; - } - - var relativePath = nconf.get('relative_path'); - if (relativePath !== '') { - cookie.path = relativePath; - } + const cookie = meta.configs.cookie.get(); + const ttl = meta.getSessionTTLSeconds() * 1000; + cookie.maxAge = ttl; return cookie; } diff --git a/test/api.js b/test/api.js new file mode 100644 index 0000000000..fa88a5b990 --- /dev/null +++ b/test/api.js @@ -0,0 +1,230 @@ +'use strict'; + +const assert = require('assert'); +const path = require('path'); +const SwaggerParser = require('@apidevtools/swagger-parser'); +const request = require('request-promise-native'); +const nconf = require('nconf'); + +const db = require('./mocks/databasemock'); +const helpers = require('./helpers'); +const user = require('../src/user'); +const groups = require('../src/groups'); +const categories = require('../src/categories'); +const topics = require('../src/topics'); +const plugins = require('../src/plugins'); +const flags = require('../src/flags'); +const messaging = require('../src/messaging'); + +describe('Read API', async () => { + let readApi = false; + const apiPath = path.resolve(__dirname, '../public/openapi/read.yaml'); + let jar; + let setup = false; + const unauthenticatedRoutes = ['/api/login', '/api/register']; // Everything else will be called with the admin user + + async function dummySearchHook(data) { + return [1]; + } + + after(async function () { + plugins.unregisterHook('core', 'filter:search.query', dummySearchHook); + }); + + async function setupData() { + if (setup) { + return; + } + + // Create sample users + const adminUid = await user.create({ username: 'admin', password: '123456', email: 'test@example.org' }); + const unprivUid = await user.create({ username: 'unpriv', password: '123456', email: 'unpriv@example.org' }); + await groups.join('administrators', adminUid); + + // Create a category + const testCategory = await categories.create({ name: 'test' }); + + // Post a new topic + const testTopic = await topics.post({ + uid: adminUid, + cid: testCategory.cid, + title: 'Test Topic', + content: 'Test topic content', + }); + + // Create a sample flag + await flags.create('post', 1, unprivUid, 'sample reasons', Date.now()); + + // Create a new chat room + await messaging.newRoom(1, [2]); + + // Attach a search hook so /api/search is enabled + plugins.registerHook('core', { + hook: 'filter:search.query', + method: dummySearchHook, + }); + + jar = await helpers.loginUser('admin', '123456'); + setup = true; + } + + it('should pass OpenAPI v3 validation', async () => { + try { + await SwaggerParser.validate(apiPath); + } catch (e) { + assert.ifError(e); + } + }); + + readApi = await SwaggerParser.dereference(apiPath); + + // Iterate through all documented paths, make a call to it, and compare the result body with what is defined in the spec + const paths = Object.keys(readApi.paths); + + paths.forEach((path) => { + let schema; + let response; + let url; + const headers = {}; + const qs = {}; + + function compare(schema, response, context) { + let required = []; + const additionalProperties = schema.hasOwnProperty('additionalProperties'); + + if (schema.allOf) { + schema = schema.allOf.reduce((memo, obj) => { + required = required.concat(obj.required ? obj.required : Object.keys(obj.properties)); + memo = { ...memo, ...obj.properties }; + return memo; + }, {}); + } else if (schema.properties) { + required = schema.required || Object.keys(schema.properties); + schema = schema.properties; + } else { + // If schema contains no properties, check passes + return; + } + + // Compare the schema to the response + required.forEach((prop) => { + if (schema.hasOwnProperty(prop)) { + assert(response.hasOwnProperty(prop), '"' + prop + '" is a required property (path: ' + path + ', context: ' + context + ')'); + + // Don't proceed with type-check if the value could possibly be unset (nullable: true, in spec) + if (response[prop] === null && schema[prop].nullable === true) { + return; + } + + // Therefore, if the value is actually null, that's a problem (nullable is probably missing) + assert(response[prop] !== null, '"' + prop + '" was null, but schema does not specify it to be a nullable property (path: ' + path + ', context: ' + context + ')'); + + switch (schema[prop].type) { + case 'string': + assert.strictEqual(typeof response[prop], 'string', '"' + prop + '" was expected to be a string, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + break; + case 'boolean': + assert.strictEqual(typeof response[prop], 'boolean', '"' + prop + '" was expected to be a boolean, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + break; + case 'object': + assert.strictEqual(typeof response[prop], 'object', '"' + prop + '" was expected to be an object, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + compare(schema[prop], response[prop], context ? [context, prop].join('.') : prop); + break; + case 'array': + assert.strictEqual(Array.isArray(response[prop]), true, '"' + prop + '" was expected to be an array, but was ' + typeof response[prop] + ' instead (path: ' + path + ', context: ' + context + ')'); + + if (schema[prop].items) { + // Ensure the array items have a schema defined + assert(schema[prop].items.type || schema[prop].items.allOf, '"' + prop + '" is defined to be an array, but its items have no schema defined (path: ' + path + ', context: ' + context + ')'); + + // Compare types + if (schema[prop].items.type === 'object' || Array.isArray(schema[prop].items.allOf)) { + response[prop].forEach((res) => { + compare(schema[prop].items, res, context ? [context, prop].join('.') : prop); + }); + } else if (response[prop].length) { // for now + response[prop].forEach((item) => { + assert.strictEqual(typeof item, schema[prop].items.type, '"' + prop + '" should have ' + schema[prop].items.type + ' items, but found ' + typeof items + ' instead (path: ' + path + ', context: ' + context + ')'); + }); + } + } + break; + } + } + }); + + // Compare the response to the schema + Object.keys(response).forEach((prop) => { + if (additionalProperties) { // All bets are off + return; + } + + assert(schema[prop], '"' + prop + '" was found in response, but is not defined in schema (path: ' + path + ', context: ' + context + ')'); + }); + } + + // TOXO: fix -- premature exit for POST-only routes + if (!readApi.paths[path].get) { + return; + } + + it('should have examples when parameters are present', () => { + const parameters = readApi.paths[path].get.parameters; + let testPath = path; + if (parameters) { + parameters.forEach((param) => { + assert(param.example !== null && param.example !== undefined, path + ' has parameters without examples'); + + switch (param.in) { + case 'path': + testPath = testPath.replace('{' + param.name + '}', param.example); + break; + case 'header': + headers[param.name] = param.example; + break; + case 'query': + qs[param.name] = param.example; + break; + } + }); + } + + url = nconf.get('url') + testPath; + }); + + it('should resolve with a 200 when called', async () => { + await setupData(); + + try { + response = await request(url, { + jar: !unauthenticatedRoutes.includes(path) ? jar : undefined, + json: true, + headers: headers, + qs: qs, + }); + } catch (e) { + assert(!e, path + ' resolved with ' + e.message); + } + }); + + // Recursively iterate through schema properties, comparing type + it('response should match schema definition', () => { + const has200 = readApi.paths[path].get.responses['200']; + if (!has200) { + return; + } + + const hasJSON = has200.content && has200.content['application/json']; + if (hasJSON) { + schema = readApi.paths[path].get.responses['200'].content['application/json'].schema; + compare(schema, response, 'root'); + } + + // TODO someday: text/csv, binary file type checking? + }); + }); +}); + +describe('Write API', () => { + let writeApi; +}); diff --git a/test/flags.js b/test/flags.js index 95ac38a3fa..f4ad852697 100644 --- a/test/flags.js +++ b/test/flags.js @@ -1,17 +1,19 @@ 'use strict'; -var assert = require('assert'); -var async = require('async'); +const assert = require('assert'); +const async = require('async'); +const util = require('util'); +const sleep = util.promisify(setTimeout); -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'); -var Privileges = require('../src/privileges'); +const db = require('./mocks/databasemock'); +const Flags = require('../src/flags'); +const Categories = require('../src/categories'); +const Topics = require('../src/topics'); +const Posts = require('../src/posts'); +const User = require('../src/user'); +const Groups = require('../src/groups'); +const Meta = require('../src/meta'); +const Privileges = require('../src/privileges'); describe('Flags', function () { let uid1; @@ -363,6 +365,28 @@ describe('Flags', function () { const state = await db.getObjectField('flag:1', 'state'); assert.strictEqual('wip', state); }); + + it('should rescind notification if flag is resolved', async () => { + const SocketFlags = require('../src/socket.io/flags.js'); + const result = await Topics.post({ + cid: category.cid, + uid: uid3, + title: 'Topic to flag', + content: 'This is flaggable content', + }); + const flagId = await SocketFlags.create({ uid: uid1 }, { type: 'post', id: result.postData.pid, reason: 'spam' }); + await sleep(2000); + + let userNotifs = await User.notifications.getAll(adminUid); + assert(userNotifs.includes('flag:post:' + result.postData.pid + ':uid:' + uid1)); + + await Flags.update(flagId, adminUid, { + state: 'resolved', + }); + + userNotifs = await User.notifications.getAll(adminUid); + assert(!userNotifs.includes('flag:post:' + result.postData.pid + ':uid:' + uid1)); + }); }); describe('.getTarget()', function () { diff --git a/test/groups.js b/test/groups.js index ee4f91f565..02a70913d1 100644 --- a/test/groups.js +++ b/test/groups.js @@ -1467,33 +1467,6 @@ describe('Groups', function () { }); }); - it('should error if user is not owner of group', function (done) { - helpers.loginUser('regularuser', '123456', function (err, jar, csrf_token) { - assert.ifError(err); - helpers.uploadFile(nconf.get('url') + '/api/groups/uploadpicture', logoPath, { params: JSON.stringify({ groupName: 'Test' }) }, jar, csrf_token, function (err, res, body) { - assert.ifError(err); - assert.equal(res.statusCode, 500); - assert.equal(body.error, '[[error:no-privileges]]'); - done(); - }); - }); - }); - - it('should upload group cover with api route', function (done) { - helpers.loginUser('admin', '123456', function (err, jar, csrf_token) { - assert.ifError(err); - helpers.uploadFile(nconf.get('url') + '/api/groups/uploadpicture', logoPath, { params: JSON.stringify({ groupName: 'Test' }) }, jar, csrf_token, function (err, res, body) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) { - assert.ifError(err); - assert.equal(nconf.get('relative_path') + body[0].url, groupData['cover:url']); - done(); - }); - }); - }); - }); - it('should fail to remove cover if not logged in', function (done) { socketGroups.cover.remove({ uid: 0 }, { groupName: 'Test' }, function (err) { assert.equal(err.message, '[[error:no-privileges]]'); diff --git a/test/helpers/index.js b/test/helpers/index.js index f1cf53d071..554ffb813c 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -66,8 +66,9 @@ helpers.logoutUser = function (jar, callback) { helpers.connectSocketIO = function (res, callback) { var io = require('socket.io-client'); - - var cookie = res.headers['set-cookie'][0].split(';')[0]; + let cookies = res.headers['set-cookie']; + cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c)); + const cookie = cookies[0]; var socket = io(nconf.get('base_url'), { path: nconf.get('relative_path') + '/socket.io', extraHeaders: { diff --git a/test/messaging.js b/test/messaging.js index cc2b101676..e663993a6a 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -692,14 +692,9 @@ describe('Messaging Library', function () { it('should not show deleted message to other users', function (done) { socketModules.chats.getMessages({ uid: herpUid }, { uid: herpUid, roomId: roomId, start: 0 }, function (err, messages) { assert.ifError(err); - - // Reduce messages to their mids - var mids = messages.reduce(function (mids, cur) { - mids.push(cur.messageId); - return mids; - }, []); - - assert(!mids.includes(mid)); + messages.forEach(function (msg) { + assert(!msg.deleted || msg.content === '[[modules:chat.message-deleted]]', msg.content); + }); done(); }); }); diff --git a/test/meta.js b/test/meta.js index 502cafbc95..04c1bd0000 100644 --- a/test/meta.js +++ b/test/meta.js @@ -93,6 +93,96 @@ describe('meta', function () { }); }); }); + + it('should return null if setting field does not exist', async function () { + const val = await meta.settings.getOne('some:hash', 'does not exist'); + assert.strictEqual(val, null); + }); + + const someList = [ + { name: 'andrew', status: 'best' }, + { name: 'baris', status: 'wurst' }, + ]; + const anotherList = []; + + it('should set setting with sorted list', function (done) { + socketAdmin.settings.set({ uid: fooUid }, { hash: 'another:hash', values: { foo: '1', derp: 'value', someList: someList, anotherList: anotherList } }, function (err) { + if (err) { + return done(err); + } + + db.getObject('settings:another:hash', function (err, data) { + if (err) { + return done(err); + } + + assert.equal(data.foo, '1'); + assert.equal(data.derp, 'value'); + assert.equal(data.someList, undefined); + assert.equal(data.anotherList, undefined); + done(); + }); + }); + }); + + it('should get setting with sorted list', function (done) { + socketAdmin.settings.get({ uid: fooUid }, { hash: 'another:hash' }, function (err, data) { + assert.ifError(err); + assert.equal(data.foo, '1'); + assert.equal(data.derp, 'value'); + assert.deepEqual(data.someList, someList); + assert.deepEqual(data.anotherList, anotherList); + done(); + }); + }); + + it('should not set setting if not empty', function (done) { + meta.settings.setOnEmpty('some:hash', { foo: 2 }, function (err) { + assert.ifError(err); + db.getObject('settings:some:hash', function (err, data) { + assert.ifError(err); + assert.equal(data.foo, '1'); + assert.equal(data.derp, 'value'); + done(); + }); + }); + }); + + it('should not set setting with sorted list if not empty', function (done) { + meta.settings.setOnEmpty('another:hash', { foo: anotherList }, function (err) { + assert.ifError(err); + socketAdmin.settings.get({ uid: fooUid }, { hash: 'another:hash' }, function (err, data) { + assert.ifError(err); + assert.equal(data.foo, '1'); + assert.equal(data.derp, 'value'); + done(); + }); + }); + }); + + it('should set setting with sorted list if empty', function (done) { + meta.settings.setOnEmpty('another:hash', { empty: someList }, function (err) { + assert.ifError(err); + socketAdmin.settings.get({ uid: fooUid }, { hash: 'another:hash' }, function (err, data) { + assert.ifError(err); + assert.equal(data.foo, '1'); + assert.equal(data.derp, 'value'); + assert.deepEqual(data.empty, someList); + done(); + }); + }); + }); + + it('should set one and get one sorted list', function (done) { + meta.settings.setOne('another:hash', 'someList', someList, function (err) { + assert.ifError(err); + meta.settings.getOne('another:hash', 'someList', function (err, _someList) { + assert.ifError(err); + assert.deepEqual(_someList, someList); + done(); + }); + }); + }); }); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 8a27d465f8..0000000000 --- a/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ ---reporter dot ---timeout 25000 ---exit ---bail \ No newline at end of file diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 2ec2fc7921..ffa3124355 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -221,12 +221,12 @@ async function giveDefaultGlobalPrivileges() { async function enableDefaultPlugins() { winston.info('Enabling default plugins\n'); - + const testPlugins = Array.isArray(nconf.get('test_plugins')) ? nconf.get('test_plugins') : []; const defaultEnabled = [ 'nodebb-plugin-dbsearch', 'nodebb-plugin-soundpack-default', 'nodebb-widget-essentials', - ]; + ].concat(testPlugins); winston.info('[install/enableDefaultPlugins] activating default plugins', defaultEnabled); diff --git a/test/plugins-installed.js b/test/plugins-installed.js new file mode 100644 index 0000000000..0401248f69 --- /dev/null +++ b/test/plugins-installed.js @@ -0,0 +1,21 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const db = require('./mocks/databasemock'); + +const installedPlugins = fs.readdirSync(path.join(__dirname, '../node_modules')) + .filter(p => p.startsWith('nodebb-')); + +describe('Installed Plugins', function () { + installedPlugins.forEach((plugin) => { + const pathToTests = path.join(__dirname, '../node_modules', plugin, 'test'); + try { + require(pathToTests); + } catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') { + console.log(err.stack); + } + } + }); +}); diff --git a/test/topics.js b/test/topics.js index c91395407b..682ce72025 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1071,7 +1071,7 @@ describe('Topic\'s', function () { assert.ifError(err); assert.equal(response.statusCode, 200); assert(body); - assert.deepEqual(body, { + assert.deepEqual(body.pagination, { prev: { page: 1, active: false }, next: { page: 1, active: false }, first: { page: 1, active: true }, diff --git a/test/uploads.js b/test/uploads.js index ff303a20d3..cbeb72b4a9 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -71,17 +71,6 @@ describe('Upload Controllers', function () { }); }); - it('should upload a profile picture', function (done) { - helpers.uploadFile(nconf.get('url') + '/api/user/regular/uploadpicture', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - assert(Array.isArray(body)); - assert.equal(body.length, 1); - assert.equal(body[0].url, '/assets/uploads/profile/' + regularUid + '-profileavatar.png'); - done(); - }); - }); - it('should upload an image to a post', function (done) { helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) { assert.ifError(err); diff --git a/test/user.js b/test/user.js index 66654cd649..ca4ce837a2 100644 --- a/test/user.js +++ b/test/user.js @@ -11,6 +11,7 @@ var db = require('./mocks/databasemock'); var User = require('../src/user'); var Topics = require('../src/topics'); var Categories = require('../src/categories'); +var Posts = require('../src/posts'); var Password = require('../src/password'); var groups = require('../src/groups'); var helpers = require('./helpers'); @@ -412,6 +413,40 @@ describe('User', function () { }); }); }); + + it('should not re-add user to users:postcount if post is deleted after user deletion', async function () { + const uid = await User.create({ username: 'olduserwithposts' }); + assert(await db.isSortedSetMember('users:postcount', uid)); + + const result = await Topics.post({ + uid: uid, + title: 'old user topic', + content: 'old user topic post content', + cid: testCid, + }); + assert.equal(await db.sortedSetScore('users:postcount', uid), 1); + await User.deleteAccount(uid); + assert(!await db.isSortedSetMember('users:postcount', uid)); + await Posts.purge(result.postData.pid, 1); + assert(!await db.isSortedSetMember('users:postcount', uid)); + }); + + it('should not re-add user to users:reputation if post is upvoted after user deletion', async function () { + const uid = await User.create({ username: 'olduserwithpostsupvote' }); + assert(await db.isSortedSetMember('users:reputation', uid)); + + const result = await Topics.post({ + uid: uid, + title: 'old user topic', + content: 'old user topic post content', + cid: testCid, + }); + assert.equal(await db.sortedSetScore('users:reputation', uid), 0); + await User.deleteAccount(uid); + assert(!await db.isSortedSetMember('users:reputation', uid)); + await Posts.upvote(result.postData.pid, 1); + assert(!await db.isSortedSetMember('users:reputation', uid)); + }); }); describe('passwordReset', function () { @@ -1446,6 +1481,18 @@ describe('User', function () { }); }); + it('should fail to delete user if account deletion is not allowed', async function () { + const oldValue = meta.config.allowAccountDeletion; + meta.config.allowAccountDeletion = 0; + const uid = await User.create({ username: 'tobedeleted' }); + try { + await socketUser.deleteAccount({ uid: uid }, {}); + } catch (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + } + meta.config.allowAccountDeletion = oldValue; + }); + it('should fail if data is invalid', function (done) { socketUser.emailExists({ uid: testUid }, null, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); diff --git a/test/utils.js b/test/utils.js index 7ba317d0f2..5349418816 100644 --- a/test/utils.js +++ b/test/utils.js @@ -3,7 +3,7 @@ var assert = require('assert'); var JSDOM = require('jsdom').JSDOM; -var utils = require('./../public/src/utils.js'); +var utils = require('../public/src/utils.js'); const db = require('./mocks/databasemock'); describe('Utility Methods', function () {